React
useReducer
Explica cómo useReducer centraliza transiciones de estado mediante acciones y reducers puros, útil cuando varias actualizaciones pertenecen al mismo flujo.
- Última actualización
- Actualizada
- Nivel
- Aplicación
React
Explica cómo useReducer centraliza transiciones de estado mediante acciones y reducers puros, útil cuando varias actualizaciones pertenecen al mismo flujo.
useReducer organiza transiciones de estado alrededor de acciones explícitas. Es útil cuando varias actualizaciones pertenecen al mismo dominio y conviene separarlas de la interfaz.
type State = {
items: CartItem[];
};
type Action =
| { type: "itemAdded"; item: Product }
| { type: "itemRemoved"; itemId: string }
| { type: "cartCleared" };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "itemAdded":
return { ...state, items: [...state.items, action.item] };
case "itemRemoved":
return {
...state,
items: state.items.filter((item) => item.id !== action.itemId),
};
case "cartCleared":
return { items: [] };
}
}const [state, dispatch] = useReducer(reducer, { items: [] });
dispatch({ type: "itemAdded", item: product });Un reducer no hace fetch, no escribe en storage y no muta el estado. Calcula el siguiente estado a partir del anterior y la acción.
Prefiere nombres que describan qué ocurrió:
dispatch({ type: "quantityChanged", itemId, quantity });frente a setters genéricos que exponen la estructura interna.
No almacenes total o itemCount si se calculan desde items:
const total = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0,
);const [state, dispatch] = useReducer(reducer, initialArg, createInitialState);La tercera función permite inicialización perezosa.
Usa useState para valores independientes y transiciones simples. Considera useReducer cuando las reglas de cambio necesitan un vocabulario propio, pruebas aisladas o varias ramas coordinadas.
Puede distribuir estado y dispatch, pero no convierte automáticamente el diseño en eficiente. Separa contextos o usa selectores si el dominio crece.
state y devolverlo.SET_STATE.useReducer mejora claridad cuando el problema tiene transiciones reconocibles. La complejidad debe vivir en reglas puras, no dispersa entre handlers.
useReducer separa qué ocurrió de cómo cambia el estado.
interacción
↓ dispatch(action)
reducer(state, action)
↓
next state
↓
renderLa UI no necesita conocer cada detalle de actualización. Expresa eventos del dominio:
dispatch({ type: "itemQuantityChanged", itemId, quantity });El reducer concentra invariantes como cantidades mínimas, límites de stock o eliminación de líneas vacías.
function assertNever(value: never): never {
throw new Error(`Unhandled action: ${JSON.stringify(value)}`);
}
function reducer(state: State, action: Action): State {
switch (action.type) {
case "itemAdded":
return addItem(state, action.item);
case "itemRemoved":
return removeItem(state, action.itemId);
case "cartCleared":
return initialState;
default:
return assertNever(action);
}
}Una unión discriminada permite que el compilador detecte acciones no manejadas.
Un reducer es una función pura. Puede utilizarse localmente en un formulario, editor o feature sin crear un store global, middleware ni una arquitectura completa.
function createInitialState(productId: string): State {
return {
productId,
quantity: 1,
notes: "",
};
}
const [state, dispatch] = useReducer(
reducer,
productId,
createInitialState,
);La función inicializadora se usa al montar. Cambiar productId no reinicia automáticamente el reducer; utiliza una key cuando representa otra entidad o despacha una acción de dominio si el reset pertenece a la misma instancia.
async function handleCheckout() {
const order = await createOrder(state);
dispatch({ type: "checkoutSucceeded", orderId: order.id });
}La operación externa ocurre en el handler. El reducer solo procesa el resultado. Esto mantiene las transiciones deterministas y fáciles de probar.
it("removes an item", () => {
const next = reducer(stateWithTwoItems, {
type: "itemRemoved",
itemId: "a",
});
expect(next.items).toEqual([itemB]);
expect(stateWithTwoItems.items).toHaveLength(2);
});Prueba:
No necesitas montar React para probar la función pura.
setState con acciones SET_X sin reglas.Distribuir state y dispatch puede coordinar una feature. Considera separar contextos para que consumidores que solo despachan no dependan de cada cambio de state. Para actualizaciones frecuentes y selectivas, una store externa puede ser más adecuada.
Un reducer está mal diseñado cuando:
SET_STATE es una acción débil?useState sigue siendo más claro?useContext explica cómo distribuir estado o dependencias sin convertir automáticamente el reducer en global.