Fundamentos de Redux, Parte 2: Conceptos y Flujo de Datos
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
- Términos y conceptos clave para usar Redux
- Cómo fluyen los datos en una aplicación Redux
Introducción
En la Parte 1: Descripción general de Redux, explicamos qué es Redux, por qué podrías querer usarlo y enumeramos las otras bibliotecas Redux que se suelen usar con el núcleo de Redux. También vimos un pequeño ejemplo de cómo es una aplicación Redux en funcionamiento y las piezas que componen la aplicación. Finalmente, mencionamos brevemente algunos de los términos y conceptos utilizados con Redux.
En esta sección, examinaremos esos términos y conceptos con más detalle y hablaremos más sobre cómo fluyen los datos en una aplicación Redux.
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Ten en cuenta que este tutorial muestra intencionadamente patrones de lógica de Redux de estilo antiguo que requieren más código que los patrones de "modern Redux" con Redux Toolkit que enseñamos como el enfoque correcto para construir aplicaciones con Redux hoy en día, con el fin de explicar los principios y conceptos detrás de Redux. No está pensado para ser un proyecto listo para producción.
Consulta estas páginas para aprender a usar "modern Redux" con Redux Toolkit:
- El tutorial completo de "Redux Essentials", que enseña "cómo usar Redux, de la manera correcta" con Redux Toolkit para aplicaciones del mundo real. ¡Recomendamos que todos los aprendices de Redux lean el tutorial "Essentials"!
- Redux Fundamentals, Parte 8: Modern Redux con Redux Toolkit, que muestra cómo convertir los ejemplos de bajo nivel de secciones anteriores en equivalentes modernos de Redux Toolkit
Conceptos de fondo
Antes de ver código práctico, repasemos la terminología y conceptos esenciales para usar Redux.
Gestión del estado
Considera este pequeño componente React de contador: rastrea un número en su estado local y lo incrementa al hacer clic en un botón:
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
Esta aplicación autocontenida tiene tres partes:
-
El estado, la fuente de verdad que impulsa nuestra aplicación;
-
La vista: descripción declarativa de la UI según el estado actual
-
Las acciones: eventos que ocurren por interacción del usuario y actualizan el estado
Este es un ejemplo de "flujo de datos unidireccional":
-
El estado describe la condición de la aplicación en un momento específico
-
La interfaz se renderiza basándose en ese estado
-
Cuando ocurre algo (como un clic de usuario), el estado se actualiza según lo ocurrido
-
La interfaz se vuelve a renderizar basándose en el nuevo estado

Sin embargo, esta simplicidad se complica cuando tenemos múltiples componentes que necesitan compartir y usar el mismo estado, especialmente si están ubicados en diferentes partes de la aplicación. A veces esto puede resolverse "elevando el estado" a componentes padres, pero no siempre funciona.
Una solución es extraer el estado compartido de los componentes y colocarlo en una ubicación centralizada fuera del árbol de componentes. ¡Así nuestro árbol de componentes se convierte en una gran "vista" donde cualquier componente puede acceder al estado o desencadenar acciones, sin importar su posición en el árbol!
Al definir y separar los conceptos de gestión de estado, y aplicar reglas que mantienen independencia entre vistas y estados, dotamos a nuestro código de mayor estructura y mantenibilidad.
Esta es la idea básica de Redux: un lugar centralizado para contener el estado global de tu aplicación, con patrones específicos para actualizarlo y hacer el código predecible.
Inmutabilidad
"Mutable" significa "cambiable". Si algo es "inmutable", nunca puede cambiarse.
Los objetos y arrays de JavaScript son mutables por defecto. Si creo un objeto, puedo modificar sus campos. Si creo un array, también puedo cambiar su contenido:
const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3
const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
Esto se llama mutar el objeto o array. Sigue siendo la misma referencia en memoria, pero su contenido interno ha cambiado.
Para actualizar valores inmutables, tu código debe hacer copias de objetos/arrays existentes y luego modificar esas copias.
Podemos hacerlo manualmente usando operadores de propagación de JavaScript o métodos de array que devuelven nuevas copias en lugar de mutar el original:
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3
},
b: 2
}
const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42
}
}
const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')
// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
Redux espera que todas las actualizaciones de estado se realicen de forma inmutable. Veremos dónde y por qué esto es importante un poco más adelante, así como algunas formas más sencillas de escribir lógica de actualización inmutable.
Para más información sobre inmutabilidad en JavaScript:
Terminología de Redux
Antes de continuar, hay algunos términos importantes de Redux con los que debes familiarizarte:
Acciones
Una acción es un objeto JavaScript plano con un campo type. Puedes pensar en una acción como un evento que describe algo ocurrido en la aplicación.
El campo type debe ser un string descriptivo como "todos/todoAdded". Generalmente usamos el formato "domain/eventName", donde la primera parte es la categoría o funcionalidad, y la segunda el evento específico.
Un objeto de acción puede tener otros campos con información adicional. Por convención, esta información va en un campo llamado payload.
Un objeto de acción típico se vería así:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
Reductores
Un reductor es una función que recibe el state actual y un objeto action, decide cómo actualizar el estado si es necesario, y devuelve el nuevo estado: (state, action) => newState. Puedes pensar en un reductor como un manejador de eventos que responde según el tipo de acción recibida.
El nombre "reductor" viene por su similitud con la función callback de Array.reduce().
Los reducers deben siempre seguir ciertas reglas específicas:
-
Deben calcular el nuevo valor de estado basándose únicamente en los argumentos
stateyaction -
No pueden modificar el
stateexistente. En su lugar, deben realizar actualizaciones inmutables, copiando elstateexistente y haciendo cambios en los valores copiados -
No deben realizar lógica asíncrona, calcular valores aleatorios ni causar otros "efectos secundarios"
Hablaremos más sobre las reglas de los reducers más adelante, incluyendo por qué son importantes y cómo seguirlas correctamente.
La lógica dentro de las funciones reducer suele seguir la misma secuencia de pasos:
-
Verificar si al reducer le importa esta acción
- Si es así, hacer una copia del estado, actualizar la copia con nuevos valores y devolverla
-
De lo contrario, devolver el estado existente sin cambios
Aquí tienes un pequeño ejemplo de un reducer que muestra los pasos que debe seguir:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/incremented') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
Los reducers pueden usar cualquier tipo de lógica interna para determinar el nuevo estado: if/else, switch, bucles, etc.
Detailed Explanation: Why Are They Called 'Reducers?'
The Array.reduce() method lets you take an array of values, process each item in the array one at a time, and return a single final result. You can think of it as "reducing the array down to one value".
Array.reduce() takes a callback function as an argument, which will be called one time for each item in the array. It takes two arguments:
previousResult, the value that your callback returned last timecurrentItem, the current item in the array
The first time that the callback runs, there isn't a previousResult available, so we need to also pass in an initial value that will be used as the first previousResult.
If we wanted to add together an array of numbers to find out what the total is, we could write a reduce callback that looks like this:
const numbers = [2, 5, 8]
const addNumbers = (previousResult, currentItem) => {
console.log({ previousResult, currentItem })
return previousResult + currentItem
}
const initialValue = 0
const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}
console.log(total)
// 15
Notice that this addNumbers "reduce callback" function doesn't need to keep track of anything itself. It takes the previousResult and currentItem arguments, does something with them, and returns a new result value.
A Redux reducer function is exactly the same idea as this "reduce callback" function! It takes a "previous result" (the state), and the "current item" (the action object), decides a new state value based on those arguments, and returns that new state.
If we were to create an array of Redux actions, call reduce(), and pass in a reducer function, we'd get a final result the same way:
const actions = [
{ type: 'counter/incremented' },
{ type: 'counter/incremented' },
{ type: 'counter/incremented' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
We can say that Redux reducers reduce a set of actions (over time) into a single state. The difference is that with Array.reduce() it happens all at once, and with Redux, it happens over the lifetime of your running app.
Store (Almacén)
El estado actual de la aplicación Redux reside en un objeto llamado store.
El store se crea pasando un reducer, y tiene un método llamado getState que devuelve el valor del estado actual:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
El store de Redux reúne el estado, las acciones y los reductores que componen tu aplicación.
El store de Redux tiene un método llamado dispatch. La única forma de actualizar el estado es llamar a store.dispatch() y pasarle un objeto de acción. El store ejecutará su función reducer y guardará el nuevo valor del estado internamente, y podemos llamar a getState() para obtener el valor actualizado:
store.dispatch({ type: 'counter/incremented' })
console.log(store.getState())
// {value: 1}
Puedes pensar en despachar acciones como "disparar un evento" en la aplicación. Algo sucedió y queremos que el store lo sepa. Los reducers actúan como oyentes de eventos, y cuando detectan una acción que les interesa, actualizan el estado en respuesta.
Dispatch (Despacho)
Los selectors son funciones que saben extraer información específica del valor del estado del store. A medida que una aplicación crece, esto evita repetir lógica cuando diferentes partes necesitan acceder a los mismos datos:
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
Selectores
Conceptos y Principios Básicos
Fuente única de la verdad
El estado global de tu aplicación se almacena como un objeto dentro de un único store. Cualquier dato debe existir solo en una ubicación, en lugar de estar duplicado en muchos lugares.
Esto facilita la depuración y la inspección del estado de tu aplicación a medida que cambia, además de centralizar la lógica que necesita interactuar con toda la aplicación.
Esto no significa que cada pieza de estado de tu aplicación deba ir al store de Redux. Debes decidir si una pieza de estado pertenece a Redux o a tus componentes de UI, según dónde se necesite.
El estado es de solo lectura
La única forma de cambiar el estado es despachar una acción, un objeto que describe lo que ocurrió.
De esta manera, la UI no sobrescribirá datos por accidente, y es más fácil rastrear por qué ocurrió una actualización de estado. Dado que las acciones son objetos JS planos, se pueden registrar, serializar, almacenar y reproducir más tarde con fines de depuración o pruebas.
Los cambios se realizan con funciones reductoras puras
Para especificar cómo se actualiza el árbol de estado en función de las acciones, escribes funciones reductoras. Los reductores son funciones puras que toman el estado anterior y una acción, y devuelven el siguiente estado. Como cualquier otra función, puedes dividir los reductores en funciones más pequeñas para ayudar en el trabajo, o escribir reductores reutilizables para tareas comunes.
Flujo de Datos en una Aplicación Redux
Anteriormente mencionamos el "flujo de datos unidireccional", que describe esta secuencia para actualizar la aplicación:
-
El estado describe la condición de la aplicación en un momento específico
-
La interfaz se renderiza basándose en ese estado
-
Cuando ocurre algo (como un clic de usuario), el estado se actualiza según lo ocurrido
-
La interfaz se vuelve a renderizar basándose en el nuevo estado
Específicamente en Redux, podemos detallar estos pasos:
-
Configuración inicial:
- Se crea un store de Redux usando una función reducer raíz
- El store llama al reducer raíz una vez y guarda el valor devuelto como su
stateinicial - En el primer renderizado, los componentes acceden al estado actual del store y lo usan para decidir qué mostrar. También se suscriben a futuras actualizaciones para detectar cambios.
-
Actualizaciones:
- Ocurre algo en la aplicación, como un clic de usuario en un botón
- El código de la app despacha una acción al store de Redux, como
dispatch({type: 'counter/incremented'}) - El store ejecuta nuevamente la función reducer con el
stateanterior y laactionactual, guardando el valor devuelto como nuevostate - El store notifica a todas las partes de la UI suscritas que se ha actualizado
- Cada componente de UI que necesita datos del store verifica si las partes del estado que requiere han cambiado
- Cada componente que detecta cambios en sus datos fuerza un re-render con los nuevos datos, actualizando lo mostrado en pantalla
Así se visualiza ese flujo de datos:

Lo que has aprendido
- La filosofía de Redux se resume en tres principios
- El estado global de la app se almacena en un único store
- El estado del store es de solo lectura para el resto de la aplicación
- Las funciones reducer actualizan el estado en respuesta a acciones
- Redux usa una arquitectura de "flujo unidireccional"
- El estado describe la condición de la app en un momento dado, y la UI se renderiza según ese estado
- Cuando ocurre algo en la app:
- La UI despacha una acción
- El store ejecuta los reducers y actualiza el estado según lo ocurrido
- El store notifica a la UI que el estado ha cambiado
- La UI se vuelve a renderizar basándose en el nuevo estado
¿Qué sigue?
Ahora deberías estar familiarizado con los conceptos clave y terminología que describen las partes de una aplicación Redux.
Continuemos viendo cómo funcionan juntas estas piezas mientras construimos una nueva aplicación Redux en Parte 3: Estado, Acciones y Reductores.