[Redux] redux之combineReducers
combineReducers
combineReducer 是将众多的 reducer 合成通过键值映射的对象,并且返回一个 combination 函数传入到 createStore 中
合并后的 combination 能够调用个子 reducer,并且对 state 状态进行更新
源码:
import { ActionTypes } from "./createStore";
import isPlainObject from "lodash/isPlainObject";
import warning from "./utils/warning";
//总体上就是根据key和action生成xxx在xxx中出现错误,你需要xxx的错误信息
function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type;
  const actionDescription = (actionType && `action "${String(actionType)}"`) || "an action";
  return `Given ${actionDescription}, reducer "${key}" returned undefined. ` + `To ignore an action, you must explicitly return the previous state. ` + `If you want this reducer to hold no value, you can return null instead of undefined.`;
}
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
  const reducerKeys = Object.keys(reducers);
  const argumentName = action && action.type === ActionTypes.INIT ? "preloadedState argument passed to createStore" : "previous state received by the reducer";
  //reducer是一个空对象,没有键值
  if (reducerKeys.length === 0) {
    return "Store does not have a valid reducer. Make sure the argument passed " + "to combineReducers is an object whose values are reducers.";
  }
  //检查 value 是否是普通对象。 也就是说该对象由 Object 构造函数创建,或者 [[Prototype]] 为 null
  //https://www.html.cn/doc/lodash/#_isplainobjectvalue
  if (!isPlainObject(inputState)) {
    return `The ${argumentName} has unexpected type of "` + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join('", "')}"`;
  }
  //如果一些key在state中存在,而在reducer中不存在,则添加至unexpectedKeyCache中并输出警告信息
  const unexpectedKeys = Object.keys(inputState).filter(key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]);
  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true;
  });
  if (unexpectedKeys.length > 0) {
    return `Unexpected ${unexpectedKeys.length > 1 ? "keys" : "key"} ` + `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + `Expected to find one of the known reducer keys instead: ` + `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`;
  }
}
//判断reducer是否符合redux的标准
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key];
    // 通过ActionTypes.INIT来(oldState,action) => return newState测试reducer是否正确
    const initialState = reducer(undefined, { type: ActionTypes.INIT });
    //如果返回是undefined说明reducer内部出错,不符合使用标准
    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.`);
    }
    const type =
      "@@redux/PROBE_UNKNOWN_ACTION_" +
      Math.random()
        .toString(36)
        .substring(7)
        .split("")
        .join(".");
    // 通过随机生成type来(oldState,action) => return newState测试reducer是否正确,如果返回是undefined说明reducer内部出错,不符合使用标准
    if (typeof reducer(undefined, { 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.`);
    }
  });
}
/**
 * Turns an object whose values are different reducer functions, into a single
 * reducer function. It will call every child reducer, and gather their results
 * into a single state object, whose keys correspond to the keys of the passed
 * reducer functions.
 *
 * @param {Object} reducers An object whose values correspond to different
 * reducer functions that need to be combined into one. One handy way to obtain
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
 * undefined for any action. Instead, they should return their initial state
 * if the state passed to them was undefined, and the current state for any
 * unrecognized action.
 *
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */
//这个函数可以组合一组 reducers(对象) ,然后返回一个新的 reducer 函数给 createStore 使用
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  //用来存放过滤后的值
  const finalReducers = {};
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];
    //开发环境下如果reducer是undefined,报警
    if (process.env.NODE_ENV !== "production") {
      if (typeof reducers[key] === "undefined") {
        warning(`No reducer provided for key "${key}"`);
      }
    }
    //如果reducer不是一个函数,就过滤掉
    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key];
    }
  }
  const finalReducerKeys = Object.keys(finalReducers);
  let unexpectedKeyCache;
  if (process.env.NODE_ENV !== "production") {
    unexpectedKeyCache = {};
  }
  //第二次过滤,将finalReducer中不符合redux的标准的reducer去掉
  let shapeAssertionError;
  try {
    assertReducerShape(finalReducers);
  } catch (e) {
    shapeAssertionError = e;
  }
  //整个combineReducer就是返回一个 combination 函数,该函数将传入createStore 中
  return function combination(state = {}, action) {
    //当执行第二次过滤是出现错误,抛出
    if (shapeAssertionError) {
      throw shapeAssertionError;
    }
    if (process.env.NODE_ENV !== "production") {
      //开发环境下,报警
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
      if (warningMessage) {
        warning(warningMessage);
      }
    }
    let hasChanged = false;
    const nextState = {};
    //遍历所有的reducer来执行(oldState,action)=>newState,根据hanChanged来判断是返回新state还是旧state (性能)
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]; //当前key值
      const reducer = finalReducers[key]; //当前key值对应的函数
      const previousStateForKey = state[key]; //当前reducer的旧状态
      const nextStateForKey = reducer(previousStateForKey, action); //为reducer计算出新state
      if (typeof nextStateForKey === "undefined") {
        // 如果计算出的新state是undefiend,通过getUndefinedStateErrorMessage拼接个报错信息,抛出去
        const errorMessage = getUndefinedStateErrorMessage(key, action);
        throw new Error(errorMessage);
      }
      //将所有新计算的state组成新state树
      nextState[key] = nextStateForKey;
      //判断新state是否等于旧state,如果不同就将hasChanged设为false,代表整个状态都改变了(只要有一个不同)
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    // 根据hasChanged的值来返回就state还是新state
    return hasChanged ? nextState : state;
  };
}
Redux 之 combineReducers(reducers)详解
Redux 源码浅析系列(二):combineReducer
[Redux] redux之combineReducers的更多相关文章
- [Redux] Redux: Extracting Container Components -- AddTodo
		Code to be refactored: const AddTodo = ({ onAddClick }) => { let input; return ( <div> < ... 
- Flux --> Redux --> Redux React  入门
		本文的目的很简单,介绍Redux相关概念用法 及其在React项目中的基本使用 假设你会一些ES6.会一些React.有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识 一般来说,推 ... 
- [Redux] redux的概述
		redux 的概述 随着 javascript 单页应用的不断发展,javascript 需要管理比以往都要多的状态,管理不断变化的 state 非常困难,数据流动不断变的模糊不可预测,代码的开发与维 ... 
- [React] 11 - Redux: redux
		Ref: Redux中文文档 Ref: React 讀書會 - B團 - Level 19 Redux 深入淺出 Ref: React+Redux 分享會 Ruan Yifeng, Redux 架构: ... 
- Flux --> Redux --> Redux React 基础实例教程
		本文的目的很简单,介绍Redux相关概念用法 及其在React项目中的基本使用 假设你会一些ES6.会一些React.有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识 一般来说,推 ... 
- Flux --> Redux --> Redux React 入门 基础实例使用
		本文的目的很简单,介绍Redux相关概念用法 及其在React项目中的基本使用 假设你会一些ES6.会一些React.有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识 一般来说,推 ... 
- 基于 Redux + Redux Persist 进行状态管理的 Flutter 应用示例
		好久没在 SegmentFault 写东西,唉,也不知道 是忙还是懒,以后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,个人博客跟这里同步. 一直都在自己的 React Native 应用中使 ... 
- Redux API之combineReducers
		combineReducers(reducers) 随着应用变得复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分. combineReducers 辅助函 ... 
- [React] 14 - Redux: Redux Saga
		Ref: Build Real App with React #14: Redux Saga Ref: 聊一聊 redux 异步流之 redux-saga [入门] Ref: 从redux-thun ... 
随机推荐
- 查看Oracle中存储过程长时间被卡住的原因
			1:查V$DB_OBJECT_CACHE SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CUX_OE_ORDER_RPT_PKG' AND LOCKS!='0 ... 
- js将一篇文章中多个连续的<br>标签替换成两个连续的<br>标签
			写本文的目的是今天恰好有一个之前做SEO的同事问我怎样把一篇文章中多个连续的br标签替换成两个连续的br标签,这里就牵涉到SEO层面的问题了. 在做SEO优化的时候,其中有一个需要注意的地方就是尽量减 ... 
- C. Neko does Maths
			time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ... 
- django 源码报错
			启动django ,一直提示一个 AttributeError: 'str' object has no attribute 'decode' 哥,查了一下午google,就怕是自己判断错了,最后在一 ... 
- vue项目中引入mint-ui的方式(全部引入与按需引入)
			参考哦 https://blog.csdn.net/qq_36742720/article/details/83620584 https://jingyan.baidu.com/article/c1a ... 
- pytorch可视化工具visdom
			visdom的github repo: https://github.com/facebookresearch/visdom 知乎一个教程:https://zhuanlan.zhihu.com/p/3 ... 
- Selenium自动化测试插件—Katalon的自述
			Katalon-一款好用的selenium自动化测试插件 Selenium 框架是目前使用较广泛的开源自动化框架,一款好的.基于界面的录制工具对于初学者来说可以快速入门:对于老手来说可以提高开发自动化 ... 
- 【逆元】HDU-1576
			逆元: 同余方程 ax≡1(mod n),gcd(a,n) = 1 时有解,这时称求出的 x 为 a 的对模n的乘法逆元.(注意:如果gcd(a,n)如果不等于1则无解),解法还是利用扩展欧几里得算法 ... 
- 什么是shell和终端?
			目录 什么是shell? 什么是终端? 什么是shell? 当谈到命令时,我们实际上指的是shell.shell是一个接收由键盘输入的命令,并将其传递给操作系统来执行的程序.几乎所有的Linux发行版 ... 
- centos7启动流程(从加电开始)
			图片来自于https://blog.csdn.net/qq_27754983/article/details/75212666 1. UEFI或BIOS启动 服务器加电后,CPU 自动重置成初始状态, ... 
