在 Redux Reducer 中读取 Store 的初始状态

Posted

技术标签:

【中文标题】在 Redux Reducer 中读取 Store 的初始状态【英文标题】:Read Store's Initial State in Redux Reducer 【发布时间】:2016-02-18 09:36:19 【问题描述】:

Redux 应用中的初始状态可以通过两种方式设置:

将其作为第二个参数传递给createStore (docs link) 将其作为第一个参数传递给您的(子)reducers (docs link)

如果您将初始状态传递给您的 store,您如何从 store 中读取该状态并将其作为 reducer 中的第一个参数?

【问题讨论】:

【参考方案1】:

简而言之:将初始状态传递给 reducer 的是 Redux,您无需执行任何操作。

当您调用createStore(reducer, [initialState]) 时,您是在让 Redux 知道当第一个操作进入时要传递给 reducer 的初始状态是什么。

您提到的第二个选项仅适用于您在创建商店时未传递初始状态的情况。即

function todoApp(state = initialState, action)

只有在 Redux 没有传递状态时才会初始化状态

【讨论】:

感谢您的回复 - 在减速器组合的情况下,如果您已将初始状态应用于存储,您如何告诉子/子减速器它拥有初始状态树的哪一部分?例如,如果你有一个menuState,我如何告诉 menu reducer 从 store 中读取 state.menu 的值并将其用作其初始状态? 谢谢,但我的问题一定不清楚。我会等着看其他人是否回复,然后再编辑。【参考方案2】:

我希望这能回答您的请求(我理解为在传递 intialState 并返回该状态时初始化减速器)

这就是我们的做法(警告:从 Typescript 代码复制而来)。

它的要点是 mainReducer(factory) 函数中的if(!state) 测试

function getInitialState(): MainState 

     return 
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     




const reducer = combineReducers(
    
        main:     mainReducer( getInitialState() ),
        ...
    
)



const mainReducer = ( initialState: MainState ): Reducer => 

    return ( state: MainState, action: Action ): MainState => 

        if ( !state ) 
            return initialState
        

        console.log( 'Main reducer action: ', action ) 

        switch ( action.type ) 
            ....
        
    

【讨论】:

【参考方案3】:

如何从存储中读取该状态并将其作为减速器中的第一个参数?

combineReducers() 为您完成这项工作。 第一种写法没什么用:

const rootReducer = combineReducers( todos, users )

但另一个,也就是等价的更清楚:

function rootReducer(state, action) 
   todos: todos(state.todos, action),
   users: users(state.users, action)

【讨论】:

【参考方案4】:

TL;DR

如果没有 combineReducers() 或类似的手动代码,initialState 总是在减速器中胜过 state = ...,因为传递给减速器的 state initialState不是 undefined,所以 ES6 参数语法在这种情况下不适用。

combineReducers() 的行为更加微妙。在initialState 中指定状态的那些reducer 将收到state。其他 reducer 将收到 undefined,因此将退回到他们指定的 state = ... 默认参数。

一般来说,initialState 会胜过reducer 指定的状态。这让 reducer 可以将 对它们有意义的初始数据 指定为默认参数,但也允许在您从某个持久性存储或服务器对存储进行水合时(全部或部分)加载现有数据。强>

首先让我们考虑一个只有一个减速器的情况。 假设你不使用combineReducers()

那么你的 reducer 可能看起来像这样:

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

现在假设您使用它创建了一个商店。

import  createStore  from 'redux';
let store = createStore(counter);
console.log(store.getState()); // 0

初始状态为零。为什么?因为createStore 的第二个参数是undefined。这是第一次传递给减速器的state。当 Redux 初始化时,它会分派一个“虚拟”动作来填充状态。所以你的counter reducer 被调用为state 等于undefined这正是“激活”默认参数的情况。因此,state 现在按照默认的state 值 (state = 0) 变为 0。将返回此状态 (0)。

让我们考虑一个不同的场景:

import  createStore  from 'redux';
let store = createStore(counter, 42);
console.log(store.getState()); // 42

为什么这次是42,而不是0?因为createStore 是用42 作为第二个参数调用的。这个参数变成了 state 和虚拟动作一起传递给你的减速器。 这一次,state 不是未定义的(它是 42!),所以 ES6 默认参数语法无效。 state42,而 42 是从减速机。


现在让我们考虑一个使用combineReducers()的情况。 你有两个减速器:

function a(state = 'lol', action) 
  return state;


function b(state = 'wat', action) 
  return state;

combineReducers( a, b )生成的reducer长这样:

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

如果我们在没有initialState 的情况下调用createStore,它会将state 初始化为。因此,state.astate.b 在调用 ab 减速器时将是 undefinedab 减速器都将接收undefined 作为他们的 state 参数,如果它们指定默认的state 值,则将返回这些值。这就是组合 reducer 在第一次调用时返回 a: 'lol', b: 'wat' 状态对象的方式。

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

让我们考虑一个不同的场景:

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

现在我将initialState 指定为createStore() 的参数。从组合减速器返回的状态结合我为 a 减速器指定的初始状态与 'wat' 默认参数指定 b 减速器选择自己。

让我们回忆一下组合reducer的作用:

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

在这种情况下,指定了state,因此它不会回退到。这是一个对象,其 a 字段等于 'horse',但没有 b 字段。这就是为什么a 减速器收到'horse' 作为它的state 并欣然返回它,但b 减速器收到undefined 作为它的state 并因此返回它的想法默认state(在我们的示例中为'wat')。这就是我们获得 a: 'horse', b: 'wat' 的方式。


总而言之,如果您坚持 Redux 约定并在使用 undefined 作为 state 参数调用 reducer 时从 reducer 返回初始状态(实现这一点的最简单方法是指定 state ES6 默认参数值),您将对组合减速器有一个很好的有用行为。 他们会更喜欢你传递给createStore()函数的initialState对象中的对应值,但是如果你没有传递任何值,或者对应的字段没有设置,默认的state参数由而是选择了 reducer。 这种方法效果很好,因为它提供了现有数据的初始化和水合,但如果没有保留数据,则允许各个 reducer 重置其状态。当然,您可以递归地应用此模式,因为您可以在多个级别上使用 combineReducers(),甚至可以通过调用 reducer 并为它们提供状态树的相关部分来手动组合 reducer。

【讨论】:

感谢您提供的详细信息 - 正是我想要的。您的 API 的灵活性非常出色。我没有看到的部分是“子”减速器将根据combineReducers 中使用的密钥了解初始状态的哪一部分属于它。再次非常感谢您。 感谢您的回答,丹。如果我正确地消化了您的答案,您是说最好通过减速器的动作类型设置初始状态?例如,GET_INITIAL_STATE 并调用该操作类型的调度,比如说,一个 componentDidMount 回调? @ConAntonakos 不,我不是这个意思。我的意思是在定义减速器的地方选择初始状态,例如function counter(state = 0, action)function visibleIds(state = [], action). @DanAbramov:使用 createstore() 中的第二个参数,我如何在 SPA 页面重新加载时使用 redux 中最后存储的状态而不是初始值来重新水化状态。我想我在问如何缓存整个商店并在重新加载时保留它并将其传递给 createstore 函数。 @jasan:使用本地存储。 egghead.io/lessons/…

以上是关于在 Redux Reducer 中读取 Store 的初始状态的主要内容,如果未能解决你的问题,请参考以下文章

redux 中的 action、reducer 和 store 有啥区别?

redux reducer 不更新 store 中的状态

P05: * 创建Redux中的仓库 - store 和 reducer

Redux reducer 不更新 store/redux-promise 不解决

如何在react中用redux组织store?

Reactredux - 三大核心概念 - action - reducer - store - 异步编程 - react-redux - 调试工具 - 纯函数 - 高阶函数