Redux: 我在 createStore 中给出了初始状态,然后 combineReducers 显示我的一个减速器在初始化期间返回未定义

创建于 2017-08-23  ·  27评论  ·  资料来源: reduxjs/redux

在减速器中:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

在 blogTypeVisibilityFilter 中:

const blogTypeVisibilityFilter = (state, action)=>{
  switch (action.type) {
    case 'BLOG_TYPE_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default blogTypeVisibilityFilter;

在博客中:

const blogs = (state,action)=>{
  return state
}

在创建商店中:

const initialState = {
  blogTypeVisibilityFilter:'SHOW_ALL_BLOG',
  blogs:data.data,
}

const store = createStore(reducer,initialState,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

然后它显示错误:

Reducer "blogTypeVisibilityFilter" 在初始化期间返回未定义。 如果传递给减速器的状态未定义,则必须显式返回初始状态。 初始状态可能不是未定义的。 如果你不想为这个 reducer 设置一个值,你可以使用 null 而不是 undefined。

但是当我改变的时候

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

const todoBlog = (state={},action)=>{
  return{
    blogTypeVisibilityFilter:blogTypeVisibilityFilter(state.blogTypeVisibilityFilter,action),
    blogs:blogs(state.blogs,action)
  }
}

在减速器中它运行良好并且没有任何错误

为什么我使用 combineReducers 会出错?

最有用的评论

这是一个错误跟踪器,而不是一个支持系统。 有关使用问题,请使用 Stack Overflow 或 Reactiflux。 谢谢!

所有27条评论

这是一个错误跟踪器,而不是一个支持系统。 有关使用问题,请使用 Stack Overflow 或 Reactiflux。 谢谢!

我有同样的问题。

脚步:

  1. 图像简单的商店形状{ foo, bar }
  2. 创建像simpleReducer = state => state这样的简单减速器。
  3. reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer })组合减速器。
  4. 使用createState(reducers, { foo: Obj1, bar: Obj2 })创建具有初始值的状态。
  5. 得到错误Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

原因是assertReducerShape运行所有具有未定义状态的减速器以获取初始状态部分。 我的简单减速器返回它的参数,在这种情况下它是未定义的。 为什么Obj1 createState调用中的Obj1不被视为 foo-part 的初始状态?

@timdorr你能重新打开问题吗?

解决方法是制作simpleReducer = (state = null) => state 。 但是可以吗?

使用combineReducers ,每个切片减速器都应该“拥有”它的状态切片。 这意味着在当前切片未定义的情况下提供一个初始值,如果您没有这样做, combineReducers会指出。

“初始状态”有多重含义。 它们是reducer 的默认返回值和createStore 的第二个参数。 我觉得有问题。 你能解释为什么 combineReducers 检查 reducer 的 initialState 和 createState 不检查吗?

@VittlycombineReducers是故意自以为是,而createStore不是。 createStore简单地调用您提供给它的任何 root reducer 函数,并保存结果。 combineReducers假设如果您正在使用它,那么您正在接受一组关于状态应该如何组织以及组合的减速器应该如何表现的特定假设。

您可能想通读 #191 和 #1189,其中详细介绍了combineReducers为何有主见以及它有什么意见的历史和细节。

所以问题是(很快)“如果你结合减速器,你也会划分你的商店树,你可能会错过一些东西,或者故意只在 createStore 2nd arg 中预加载商店的一部分。为了使商店树更加一致,请使用 assertReducerShape”。 好的,谢谢参考

同样的问题在这里。
所以我使用createStore并传递一个初始状态。 但是在reduct.js文件中,函数assertReducerShape redux 将在传递undefined状态时检查我的减速器。

恕我直言,这是一个错误。

@JoseFMP :我会有效地保证您所看到的都_不是_错误。 但是,如果您可以将演示该问题的存储库或 CodeSandbox 放在一起,我们可以查看一下。

当然。 我在 Stackoverflow 中提出了一个问题:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

这个问题很简单。 所以如果我在创建 store 时设置了一个初始状态……为什么 redux 要用undefined状态来评估 reducer? 这确实使所有的减速器返回一个新状态undefined

但这是无稽之谈,因为我们传递了一个初始状态。
所以这是失败的:

import { combineReducers, createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customData = (customData: any, action: any): any =>  {
        return customData;
}
const reducers = combineReducers({config: configReducer, customData: customDataReducer})

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP

啊,我想我知道是什么问题了。 这特定于combineReducers()工作方式。

combineReducers期望您提供的所有“切片缩减器”都将遵循一些规则。 特别是,它期望 _if_ 您的减速器使用state === undefined调用,它将返回一个合适的默认值。 为了验证这一点, combineReducers()实际上会用(undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"})调用您的减速器,以查看它是否返回 undefined 或“真实”值。

您的reducer 目前除了返回传入的任何内容之外什么都不做。这意味着如果state未定义,它们将_return_ 未定义。 所以, combineReducers()告诉你你打破了它预期的结果。

只需将声明更改为configReducer = (config = {}, action)等,即可修复警告。

再次声明:这_不是_一个错误。 这是行为验证。

Hej @markerikson感谢您的回复。
几点意见:

1)不,它不是特定于combineReducers 。 如果我不使用combineReducers并实现我自己的根减速器,也会发生同样的情况。

2)这里的不一致之处在于,在 Redux 文档中提到,如果使用未知或意外状态调用 reducer,则不应修改状态并返回作为参数的值。 即......如果它收到undefined它应该返回undefined 。 但是另一条规则说reducer 永远不应该返回undefined所以这两个规则,因为目前在redux 文档中是不一致的。 并且与执行不一致。

@JoseFMP :正如我之前所说,如果您可以提供一个专门演示该问题的 CodeSandbox,并通过评论准确指出您期望发生的情况与实际发生的情况,我也许可以看看。 (另外,请指出您认为不一致的文档部分。)在那之前,我只能将其归结为对 Redux 内部工作方式的误解。

谢谢@markerikson
关于文档:
image

因此,如果使用未知操作调用减速器,我应该返回与减速器相同的状态,因为减速器不知道该减速器中的动作应该做什么。

创建 store 时,Redux 通过向它们发送未知操作和先前状态undefined检查 reducer。 所以如果之前的状态是undefined ,reducer 应该根据文档返回undefined (因为 reducer 不知道这个动作)。 但是如果 reducer 返回undefined ,我保证你没有 Redux 应用程序可以工作。

对于示例代码:

import { createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customReducer = (customData: any, action: any): any =>  {
        return customData;
}

const reducers = (currentState: IAppState, action: any): IAppState => {

    var appStateToEvaluate: any;
    if (currentState) { //needs to add this to pass the `undefined` check of redux
        appStateToEvaluate = currentState;
    }
    else{
        //why redux is doing this ?!
        appStateToEvaluate = {}
    }
    const newState: IAppState = {
        cvConfig: configReducer(appStateToEvaluate.config, action),
        personalData: customReducer(appStateToEvaluate.customData, action)
    }

    return newState;
}

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP :我认为您在这里缺少的主要区别在于,在该示例中,reducer 函数 _already_ 处理了stateundefined ,使用 ES6 默认参数语法:

function todoApp(state = initialState, action) {

所以教程中的建议是正确的 - 一个减速器_应该_总是返回现有状态,_假设未定义的情况已经被处理_。

@markerikson
谢谢您的回答。
这对我来说很清楚。 只是在教程中缺少。 在本教程中,它没有说要返回的案例是在 reducer 中指定为默认参数值的案例。 所以你用斜体表示的句子是正确的。 就这样。 我担心的是教程中缺少它。 或者我没有找到它,或者是模棱两可的。

现在,独立于教程和/或文档,我发现这个undefined检查个人对于指定初始状态的特定情况没有意义。 如果没有指定初始状态,我觉得没问题。 现在这不是讨论,只是我的意见:如果指定了初始状态,我发现进行此检查毫无意义。 但无论如何我可以(并且必须)忍受它;)

感谢您对马克的支持。

我们当然可以尝试改写一些措辞,作为我们更大的文档改造(#2590)的一部分。

也就是说,Redux 背后的原始想法之一是每个“切片缩减器”负责“拥有”自己的状态部分,其中包括更新和提供初始值。 您似乎有点坚持“好吧,我正在为createStore提供初始值”,但是您低估了 Redux 期望它应该如何表现的期望,即使您_不是_单独提供价值。

有点惊讶我还没有链接这个,但你可能想通读文档中的初始化状态页面。

@markerikson
是的,你是对的。 我已经知道有关初始状态的文档。 我的意思(我确实相信这是创建这个问题的原因,人们也朝着这个方向表示)是在创建商店时提供“初始状态”并仍然定义“默认状态”是违反直觉的切片减速器(或根减速器)。 即,因为很多时候,但不一定,它们是相同的,或者非常相关,所以必须对它们进行两次定义是违反直觉的。 以@ElonXun或@Vittly 的帖子为例,他们和我一样困惑。 即我的评论不是 Redux 的 API,而是纯粹从人类的角度来看,在这个特定场景中使用 Redux 的 API 感觉有多直观。

请注意,最后一段是关于使用 redux 的 API 时的人性化感受。 其背后的机制实施或原因可能是完全合法的。 但作为一个 API 消费者,它感到很困惑。

例如,对我来说,在开发时,我的应用程序中经常有一个初始状态。 所以通常我需要输入两次。 一次在我创建存储时插入它,另一次将它作为默认值分发到切片减速器中。 当然有很多解决方案。 但是,对于我作为人类来说,让人感到困惑的原则是我必须输入两次相同的东西。

但是我确实认为有一个特殊情况,其中设置了初始状态并且不强制要求切片减速器或根减速器具有“默认状态”比仍然强制设置更麻烦一些。

所以这里唯一的贡献是提到它感觉有点违反直觉。 但仅此而已。

我不记得combineReducers assertReducerShape在早期版本中调用undefined是我的减速器的无效状态。 我不需要 combineReducers 来为我确保这一点,我需要它来“组合减速器”并在发生变化时重新创建根对象。 这有点超出我的预期。 IMO,现在太固执了。

诚然,我有一段时间没有使用 combineReducers,因为我在开发的核心应用程序中不需要它。 但是我现在正在尝试包装该应用程序,并且认为 combineReducers 可以很好地用于对应用程序进行切片。 assertReducerShape的行为令人惊讶。

@lukescott :该支票自 2015 年 9 月以来一直存在:

https://github.com/reduxjs/redux/commit/a1485f0e30ea0ea5e023a6d0f5947bd56edff7dd

是的, combineReducers()是_故意_自以为是。 如果您不想要这些警告,那么无需这些检查就可以很容易地编写自己的类似函数。

作为一个具体的例子,请参阅 Dan 的100 行中的 gist

我知道了。 旧版本通过了初始状态并在每次运行时检查未定义(如果我记得的话)。 令人惊讶的是初始状态没有在第一次运行时传递。 传递 undefined 是我的应用程序中的一个错误。 正如我所说,我已经在这个项目上工作了一段时间,并且有一段时间没有使用 combineReducers。 我现在才开始使用它,将我们的应用程序放在一个包装器中。

我也明白这是自以为是。 但这种观点是“减速器不能返回未定义的”——这是我遵守的规则。 它已更改为“我们将传递 undefined,您必须自己从无到有创建状态”。 combineReducers 不再适用于“总是有一个初始状态”——这是不幸的。 这彻底改变了 3 年前制定的规则。

我真的不确定你指的是什么行为改变。 你能指出具体的例子吗?

初始化状态文档页面列出了createStorepreloadedState参数、reducer 的state = initialState处理和combineReducers()之间的交互。 这是基于 Dan 在 2016 年初写的 Stack Overflow 答案,据我所知,在这方面没有任何有意义的改变。

@markerikson你是对的。 我回过头来看 2.0,看起来它一直都是这样做的。 也许我的应用程序的复杂性使它更加明显。

我的问题是我的减速器被定义为:

reducer(state: State, action: Action)

这意味着该状态不能是未定义的。 这意味着必须有一个初始状态。 但是因为 combineReducers 调用 reducer(undefined, INIT) 来检查它,它会导致我的代码中出现错误(如果它成功,它稍后会调用 reducer(initState, INIT) - 调用 INIT 两次)。

这意味着在 combineReducers 中使用的任何减速器都必须定义为:

reducer(state: State | undefined, action: Action)

所以我断言它之前没有这样做是不正确的。 但是我遇到的问题和 OP 遇到的问题可能是一样的:它强制您使用可选状态声明减速器。 我实际上没有收到警告,它会导致我的代码崩溃,因为它需要一个非未定义的状态。

如果你说它一定是这样,我需要自己滚动它,那很好。 很不幸,因为除了断言形状之外,它已经在运行时检查了 undefined 。 断言形状似乎有点矫枉过正和适得其反。

是的。 如该文档页面中所述,当您使用combineReducers ,特别期望每个切片缩减器负责返回其自己的初始状态,并且它应该响应sliceReducer(undefined, action) 。 从combineReducers的角度来看,您的代码 _is_ 有问题,因为它没有遵守预期的合同,因此它告诉您代码是错误的。

您实际上没有提供初始状态吗? 代码实际上是什么样的?

我通过 createStore 提供初始状态。 但是该初始状态并未传递给我的减速器,因为尽管我传递了初始状态,assertReducerShape 还是使用undefined显式调用了我的减速器:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L68

我的代码没有问题。 它只需要 undefined _never_ 传入它。 初始状态是必需的,它是从服务器生成的。 combineReducers 只是破坏了这个契约,并且使得严格键入具有所需状态的 reducer 变得不可能。 我可以在没有 combineReducers 的情况下做到这一点,而且效果很好。 我想这就是我必须做的 - 但 IMO - 强制可选状态是不可取的,在我的情况下, combineReducers 变得无用,因为它破坏了我严格类型化的应用程序。

我不明白的是为什么 assertReducerShape 是必要的。 它已经在运行时检查undefined在这里:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L169

assertReducerShape 似乎有点多余。 但在我的情况下,打破了类型保证......这是它试图断言的东西?

那么,这真的是一个 TypeScript 交互问题吗?

老实说,这似乎最终是方法的不同。

combineReducers()是用 JS 编写的,并且正在尝试进行运行时检查。

您正在尝试在 TS 中进行静态检查,并且您编写的声明与combineReducers()实际工作方式不匹配。 因此,从这个意义上说,切片缩减器的类型声明是不正确的,因为当与combineReducers()一起使用时,它 _can_ 和 _will_ 被undefined调用。

您调用的特定行是检查切片缩减器的返回值在使用(可能有意义的)现有状态切片值调用时是否不是undefined ,而assertReducerShape()正在检查它不返回undefined当给定的初始状态值undefined当一个未知的动作类型(即调用时,(即,减速自初始化其状态),也将减速默认情况下始终返回现有状态)。

如果您未定义,则替换它......在我的情况下,当我的服务器出于任何原因回复没有数据时,可能是因为没有活动会话,我遇到了这个问题,所以在减速器中执行此操作,作为对我的魅力:

export default function itemReducer(state = initialState.items | undefined, action){ 
    switch(action.type) 
   { 
         case "LOAD_ITEMS_SUCCESS":
               if(action.items==undefined) { 
                        action.items=[]; 
               }
                return action.items

如果 reducer 未定义,则将 undefined 替换为空数组,您将不会再收到该错误。

干杯!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

ilearnio picture ilearnio  ·  3评论

cloudfroster picture cloudfroster  ·  3评论

benoneal picture benoneal  ·  3评论

elado picture elado  ·  3评论

dmitry-zaets picture dmitry-zaets  ·  3评论