在Redux Reducer中读取商店的初始状态


85

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

  • 将其作为第二个参数传递给createStoredocs link
  • 将其作为第一个参数传递给您的(子)还原器(docs链接

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

Answers:


185

TL; DR

没有combineReducers()或没有类似的手动代码,initialState总会state = ...在化简器中胜出,因为state传递给化简器的is initialStatenot undefined,因此在这种情况下不会应用ES6参数语法。

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

通常,initialState胜过减速器指定的状态。这使reducer可以将对它们有意义的初始数据指定为默认参数,但是当您从某个持久性存储或服务器对存储进行连接时,也可以(全部或部分)加载现有数据。

首先,让我们考虑一个单独的减速器的情况。
说你不使用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初始化时,它会分派“虚拟”动作来填充状态。因此,您的counterreducer被称为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被称为带有42第二个参数。该参数state与伪动作一起传递给减速器。这次state不是未定义的(是42!),因此ES6默认参数语法无效。state42,和42从减速机返回。


现在考虑使用的情况combineReducers()
您有两个减速器:

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

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

由生成的reducercombineReducers({ 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.astate.bundefined通过它调用的时间ab减速。双方ab减速会收到undefined作为他们的 state论点,如果他们指定默认state值,这些将被退回。这就是组合化归约器{ 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收到undefined其作为state并因此返回默认值的想法state(在我们的示例中为'wat')。这就是我们获得{ a: 'horse', b: 'wat' }回报的方式。


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


3
感谢您提供的详细信息-正是我想要的。您的API的灵活性非常出色。我没看到的部分是,“子级”减速器将根据中使用的键来了解属于它的初始状态的哪一部分combineReducers。再次非常感谢你。
cantera

谢谢您的回答,丹。如果我正确地理解了您的答案,那么您是在说,通过reducer的动作类型设置初始状态是最好的吗?例如,GET_INITIAL_STATE并调用该操作的分派类型,例如,componentDidMount回调?
Con Antonakos '16

@ConAntonakos不,我不是这个意思。我的意思是在定义减速器的位置选择初始状态,例如function counter(state = 0, action)function visibleIds(state = [], action)
Dan Abramov

1
@DanAbramov:使用createstore()中的第二个参数,我如何在SPA页面重载时使用Redux中的最后一个存储状态(而不是初始值)对状态进行水化处理。我猜我在问我如何缓存整个商店,并在重新加载时将其保存并传递给createstore函数。
2016年

1
@jasan:使用localStorage。egghead.io/lessons/...
丹·阿布拉莫夫

5

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

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

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

function todoApp(state = initialState, action)

仅当Redux没有传递状态时,状态才会被初始化


1
感谢您的回应-在化简器组成的情况下,如果您将初始状态应用于商店,如何告诉子/子化简器它拥有初始状态树的哪一部分?例如,如果您有一个menuState,我如何告诉菜单reducerstate.menu从存储中读取值并将其用作初始状态?
cantera 2015年

谢谢,但我的问题必须不清楚。我将等着看别人是否回应,然后再进行编辑。
cantera 2015年

1

您如何从商店中读取该状态并将其作为化简器中的第一个参数?

CombineReducers()为您完成这项工作。编写它的第一种方法并没有真正的帮助:

const rootReducer = combineReducers({ todos, users })

但是另一种,即等效是更清楚的:

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

0

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

这是我们的操作方式(警告:从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 ) {
            ....
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.