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

IT技术 javascript reactjs redux
2021-02-25 15:56:14

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

  • 将它作为第二个参数传递给createStore文档链接
  • 将它作为第一个参数传递给您的(子)reducers(文档链接

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

4个回答

TL; 博士

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

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

一般情况下,initialState胜过减速器指定的状态。这让化简器可以将对其有意义的初始数据指定为默认参数,而且还允许在您从某些持久性存储或服务器中加载存储时(全部或部分)加载现有数据。

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

那么您的减速器可能如下所示:

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

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

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

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

为什么这次42,而不是0因为createStore被称为 with42作为第二个参数。这个参数state与虚拟操作一起传递给你的减速器。这一次,state不是未定义的(它是42!),因此 ES6 默认参数语法不起作用。state42,和42从减速机返回。


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

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

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

生成的减速器combineReducers({ a, b })如下所示:

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

如果我们在createStore没有 的情况下调用initialState,它将初始化state{}因此,state.a并且state.bundefined通过它调用ab减速器的时间双方ab减速会收到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减速器自己选择默认参数结合

让我们回忆一下组合式减速器的作用:

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

在这种情况下,state已指定,因此它不会回退到{}. 它是一个a字段等于的对象'horse',但没有b字段。这就是为什么areducer 接收'horse'state并很高兴地返回它,但breducer 接收undefinedstate并因此返回的默认想法state(在我们的例子中,'wat')。这就是我们获得{ a: 'horse', b: 'wat' }回报的方式。


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

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

简而言之:Redux 是将初始状态传递给 reducer 的人,你不需要做任何事情。

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

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

function todoApp(state = initialState, action)

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

感谢您的回复 - 在 reducer 组合的情况下,如果您已将初始状态应用于 store,您如何告诉 child/sub reducer 它拥有初始状态树的哪一部分?例如,如果您有一个menuState,我如何告诉菜单缩减器state.menu从商店读取 的值并将其用作其初始状态?
2021-04-22 15:56:14
谢谢,但我的问题一定不清楚。在编辑之前,我会等着看其他人是否回应。
2021-05-14 15:56:14

你如何从 store 中读取 state 并将它作为你的 reducer 中的第一个参数?

combineReducers() 为您完成这项工作。第一种编写方法并不是很有帮助:

const rootReducer = combineReducers({ todos, users })

但另一个等价的更清楚:

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

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

我们就是这样做的(警告:从 Typescript 代码中复制)。

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

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 ) {
            ....
        }
    }
}