Ir al contenido principal
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Inicialización del estado

Existen dos formas principales de inicializar el estado en tu aplicación. El método createStore puede recibir un valor opcional preloadedState como segundo argumento. Los reductores también pueden especificar un valor inicial detectando cuando el argumento de estado recibido es undefined, devolviendo entonces el valor que desean usar por defecto. Esto puede hacerse mediante una comprobación explícita dentro del reductor o usando la sintaxis de valores por defecto en argumentos: function myReducer(state = someDefaultValue, action).

No siempre es evidente cómo interactúan estos dos enfoques. Afortunadamente, el proceso sigue reglas predecibles. Así es como encajan las piezas.

Resumen

Sin combineReducers() o código manual similar, preloadedState siempre prevalece sobre state = ... en el reductor porque el state que se pasa al reductor es preloadedState y no es undefined, por lo que la sintaxis de argumento por defecto no aplica.

Con combineReducers() el comportamiento es más matizado. Aquellos reductores cuyo estado está especificado en preloadedState recibirán dicho estado. Los demás reductores recibirán undefined y por eso mismo recurrirán al valor por defecto state = ... que hayan especificado.

En general, preloadedState prevalece sobre el estado especificado por el reductor. Esto permite que los reductores definan datos iniciales que tengan sentido para ellos como valores por defecto, pero también posibilita cargar datos existentes (total o parcialmente) cuando hidratas el store desde almacenamiento persistente o el servidor.

Nota: Los reductores cuyo estado inicial se completa mediante preloadedState aún necesitarán proporcionar un valor por defecto para manejar cuando reciban un state igual a undefined. Todos los reductores reciben undefined durante la inicialización, por lo que deben escribirse de modo que al recibir undefined, devuelvan algún valor. Puede ser cualquier valor no undefined; no es necesario duplicar aquí la sección de preloadedState como valor por defecto.

En profundidad

Reductor simple único

Primero consideremos un caso con un único reductor. Supongamos que no usas combineReducers().

Entonces tu reductor podría verse así:

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

Ahora supongamos que creas un store con él:

import { createStore } from 'redux'
const store = createStore(counter)
console.log(store.getState()) // 0

El estado inicial es cero. ¿Por qué? Porque el segundo argumento pasado a createStore fue undefined. Este es el state que recibe tu reductor la primera vez. Cuando Redux se inicializa, despacha una acción "ficticia" para poblar el estado. Así que tu reductor counter fue llamado con state igual a undefined. Este es exactamente el caso que "activa" el argumento por defecto. Por tanto, state pasa a ser 0 según el valor por defecto de state (state = 0). Este estado (0) será devuelto.

Consideremos otro escenario:

import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42

¿Por qué es 42 y no 0 esta vez? Porque createStore fue llamado con 42 como segundo argumento. Este argumento se convierte en el state pasado a tu reductor junto con la acción ficticia. Esta vez, state no es undefined (¡es 42!), así que la sintaxis de argumento por defecto no tiene efecto. El state es 42, y se devuelve 42 desde el reductor.

Reductores combinados

Ahora consideremos un caso donde usas combineReducers(). Tienes dos reductores:

function a(state = 'lol', action) {
return state
}

function b(state = 'wat', action) {
return state
}

El reductor generado por combineReducers({ a, b }) se ve así:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

Si llamamos a createStore sin preloadedState, inicializará el state como {}. Por tanto, state.a y state.b serán undefined cuando se llamen los reductores a y b. Ambos reductores a y b recibirán undefined como su argumento state, y si especifican valores predeterminados para state, esos serán retornados. Así es como el reductor combinado devuelve un objeto de estado { a: 'lol', b: 'wat' } en la primera invocación.

import { createStore } from 'redux'
const store = createStore(combined)
console.log(store.getState()) // { a: 'lol', b: 'wat' }

Consideremos otro escenario:

import { createStore } from 'redux'
const store = createStore(combined, { a: 'horse' })
console.log(store.getState()) // { a: 'horse', b: 'wat' }

Ahora especifiqué el preloadedState como argumento de createStore(). El estado devuelto por el reductor combinado fusiona el estado inicial que especifiqué para el reductor a con el argumento predeterminado 'wat' que eligió el reductor b para sí mismo.

Recordemos lo que hace el reductor combinado:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

En este caso, state estaba especificado así que no recurrió a {}. Era un objeto con campo a igual a 'horse', pero sin campo b. Por eso el reductor a recibió 'horse' como su state y lo devolvió sin cambios, mientras que el reductor b recibió undefined como su state y por tanto devolvió su valor predeterminado para state (en nuestro ejemplo, 'wat'). Así obtenemos { a: 'horse', b: 'wat' } como resultado.

Recapitulación

Resumiendo: si sigues las convenciones de Redux y devuelves el estado inicial cuando los reductores reciben undefined como argumento state (la forma más fácil es especificar el valor predeterminado del argumento state), obtendrás un comportamiento útil en reductores combinados. Preferirán el valor correspondiente en el objeto preloadedState que pases a createStore(), pero si no pasaste ninguno o el campo correspondiente no está definido, se usará el argumento state predeterminado especificado por el reductor. Este enfoque funciona bien porque permite tanto inicialización como hidratación de datos existentes, además de que reductores individuales pueden reiniciar su estado si sus datos no se preservaron. Naturalmente, puedes aplicar este patrón recursivamente usando combineReducers() en múltiples niveles o componiendo reductores manualmente pasándoles la parte relevante del árbol de estado.