redux学习之有点贱的combineReducers爬坑

这几天在看react、redux,在试用redux的combineReducers,准备动手敲一敲代码,按照书上的讲解背着敲一遍示例,结果刚到使用combineReducers创建store就无情的抛出错误,先分享个错误描述和截图:

醒目的报错:

Error: Reducer "todos" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.

贴上出错时候的代码:

1,初始化库代码:

//store.js
import {createStore, combineReducers} from "redux";
import {reducer as todosReducer} from "./todoList/reducer.js";
import {reducer as filterReducer} from "./filter/reducer.js";
/*
//库格式如下:
var initStore = {
    todos:[{
        id,
        text,
        completed
    }],
    filter:""
}*/
let initStore = {
    todos:[],
    filter:""
}
let reducer = combineReducers({
    todos:todosReducer,
    filter:filterReducer
});
export const store=createStore(reducer,initStore);

2,reducer代码:

    2.1 , todoList.js中的reducer:

//todoList.js中的reducer
import {TODO_ADD, TODO_TOGGLE,TODO_REMOVE} from "./actionType.js";
export function reducer(todosState,action){
    console.log("todoList.reducer :: enter, action = "+JSON.stringify(action));
    switch(action.type){
        case TODO_ADD:
            return [{
                id          : action.id,
                text        : action.text,
                completed   : action.completed
            },...todosState]
        case TODO_TOGGLE:
            return todosState.map(item=>{               
                if(item.id == action.id){
                    return {...item, completed : !item.completed};
                }else{
                    return {...item};
                }                
            });
        case TODO_REMOVE:
            return todosState.filter(item=>{
                item.id != action.id;
            });
        default:
            return todosState;//哈哈,这里在使用combineReducers的时候很贱
    }
}

    2.2 , filter.js中的reducer:

    一样的会报错,所以要注意default的处理。代码暂不贴了。

为啥捏??这么简单的逻辑和代码居然报错!怀疑一下这几年不敲代码手生了,但是也不至于啊。。。着实研究了一会,居然开始怀疑到view里面去了,也不行。

开F12,加console.log输出内容,再把断点加到todoList.js中的reducer的入口处,一行一行的debug着走,进入到redux的assertReducerShape之后,傻眼了,哈哈。

assertReducerShape这个方法会来断言测试传入的每一个reducer,在调用reducer的时候,自动传入的值为:状态字段是undefined, action字段的type是个很长的字符串(跟谁的都不会重复),actionType: ActionTypes.INIT (值是"@@redux/INITe.u.4.0.k.j"),断言判断reducer的时候,希望不能返回undefined, 可以是null,但是绝对不能是undefined。。。这个是啥逻辑呢?感觉很贱的测试啊。还是很疑惑为啥要做undefined的判断

因为我的todoList.js中的reducer会跟进action的type去做判断,看返回什么值,如果都不是期望的type,就直接把传入的todosState原样返回了,看上去貌似很合乎常理。但是被框架认为不符合要求,于是给了大大的红色警告!

知道问题在哪里,自然也就知道要怎么改了:todoList.js中的reducer在发现不是期望的action type的时候,如果准备原样返回传入的state,要对state做判断,不能是undefined,可以返回个null或者空数组!于是代码修改为:

//修改后的todoList/reducer.js
import {TODO_ADD, TODO_TOGGLE,TODO_REMOVE} from "./actionType.js";
export function reducer(todosState,action){
    console.log("todoList.reducer :: enter, action = "+JSON.stringify(action));
    switch(action.type){
        case TODO_ADD:
            return [{
                id          : action.id,
                text        : action.text,
                completed   : action.completed
            },...todosState]
        case TODO_TOGGLE:
            return todosState.map(item=>{               
                if(item.id == action.id){
                    return {...item, completed : !item.completed};
                }else{
                    return {...item};
                }                
            });
        case TODO_REMOVE:
            return todosState.filter(item=>{
                item.id != action.id;
            });
        default:
            return todosState === undefined ? [] : todosState; //哈哈,这里在使用combineReducers的时候很贱
    }
}

贴上redux中assertReducerShape的源码:

//来自redux源码
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(function (key) {
    var reducer = reducers[key];
    var initialState = reducer(undefined, { type: ActionTypes.INIT });
 
    if (typeof initialState === 'undefined') {
      throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined. If you don\'t want to set a value for this reducer, ' + 'you can use null instead of undefined.');
    }
 
    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
    if (typeof reducer(undefined, { type: type }) === 'undefined') {
      throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined, but can be null.');
    }
  });
}

贴上debug截图,辅助回忆:

从CSDN上看到一些关于说明,贴上来记录一下:

以下from: https://blog.csdn.net/woshizisezise/article/details/51142968

本函数设计的时候有点偏主观,就是为了避免新手犯一些常见错误。也因些我们故意设定一些规则,但如果你自己手动编写根 redcuer 时并不需要遵守这些规则。

每个传入 combineReducers 的 reducer 都需满足以下规则:

  • 所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。

  • 永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。

  • 如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为 undefined。

虽然 combineReducers 自动帮你检查 reducer 是否符合以上规则,但你也应该牢记,并尽量遵守。

猜你喜欢

转载自blog.csdn.net/dz45693/article/details/82777839