GVKun编程网logo

[Redux] redux之combineReducers

22

在本文中,您将会了解到关于[Redux]redux之combineReducers的新资讯,并给出一些关于CombineReducers源码解析、CombineReducers中的不变性、combin

在本文中,您将会了解到关于[Redux] redux之combineReducers的新资讯,并给出一些关于CombineReducers 源码解析、CombineReducers中的不变性、combineReducers使用、CombineReducers导致代码中断的实用技巧。

本文目录一览:

[Redux] redux之combineReducers

[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 的 isPlainObject 源码

CombineReducers 源码解析

CombineReducers 源码解析

为什么要用combineReducers,因为这样可以分离相应的逻辑

Reducer是管理state的一个控件,它要做的事情就是当state没有的时候,返回initalState,有的时候根据action来状态变化,这里注意它是一个纯函数,也就是它不改变传入的state
{...state, ...newState}

当我们的reducer去处理很多action的时候,我们可以根据key把它拆分开,是的逻辑更加清晰

import { VisibilityFilters, ADD_TODO, TOGGLE_TODO } from ''./actions''

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

因为todos跟visibilityFilter是分开更新的,所以其实我们可以把它们分开来

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

这样每个reducer只负责自己那一块的状态更新,combindReducers做了上面todoApp做的事情,就是返回一个大的function 接受state,action,然后根据key用不同的reducer

import { combineReducers } from ''redux''

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp
const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

注意:combinedReducer的key跟state的key一样
知道了需求之后我们来写一个自己的combindReducer吧


function combindReducer(reducers) {
    // 第一个只是先过滤一遍 把非function的reducer过滤掉
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  reducerKeys.forEach((key) => {
      if(typeof reducers[key] === ''function'') {
      finalReducers[key] = reducers[key]
      } 
  })
  const finalReducersKeys = Object.keys(finalReducers)
    // 第二步比较重要 就是将所有reducer合在一起
    // 根据key调用每个reducer,将他们的值合并在一起
    let hasChange = false;
    const nextState = {};
    return function combind(state={}, action) {
        finalReducersKeys.forEach((key) => {
            const previousValue = state[key];
            const nextValue = reducers[key](previousValue, action);
            nextState[key] = nextValue;
            hasChange = hasChange || previousValue !== nextValue
        })
        return hasChange ? nextState : state;
    }
}

CombineReducers中的不变性

CombineReducers中的不变性

如何解决CombineReducers中的不变性?

我正在阅读redux文档,试图了解不变性和纯reducer的目的,实际上我找到了一个很好的解释,说明CombineReducers的工作方式以及一切都在引起人们的兴趣。但此段:

“值得强调的是:如果所有缩减器都返回传递给它们的同一个状态对象,那么CombineReducers将返回当前的根状态对象,而不是新近更新的状态对象。”

这意味着如果我的化简器对状态对象进行了突变而不是返回新副本,则不会触发任何重新渲染。但是我尝试使用它,我采用了文档中提供的todoApp示例,并添加了一个todo,它使用了push方法来改变状态并返回状态。我期望列表不会更新,但是确实如此。所以我面临着我无法解释的矛盾。为什么会这样?

这是我改变状态的还原器:

const todos = (state = [],action) => {
  if (action.type === "ADD_Todo") {
    state.push(todo(undefined,action));
    return state;
  }
  if (action.type === "TOGGLE_Todo") return state.map((t) => todo(t,action));
  return state;
};

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

combineReducers使用

combineReducers使用

  1. 随着应用变得复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分。
    //原本一个实现加,减,重置的reducer
    function reducer(state = {count: 0}, action) {
        switch(action.type) {
            case ''Increment'':
                return {
                    count: state.count+1
                }
                break;
            case ''Decrement'':
                return {
                    count: state.count-1
                }
                break;
            case ''Reset'':
                return {
                    count: 0
                }
                break;
            default:
                return state
                break;
        }
    }

     

  2. 我们可以拆分成两个,然后使用combineReducers结合起来
    //拆分成多个reducer,然后结合
    import { combineReducers } from ''redux''
    function change(state = {count: 0}, action) {
        switch(action.type) {
            case ''Increment'':
                return {
                    count: state.count+1
                }
                break;
            case ''Decrement'':
                return {
                    count: state.count-1
                }
                break;
            default:
                return state
                break;
        }
    }
    function reset (state = {count: 0}, action) {
        switch(action.type) {
            case ''Reset'':
                return {
                    count: 0
                }
                break;
            default:
                return state
                break;
        }
    }
    export default combineReducers({
        change,
        reset
    })

     

  3. 需要注意的是我们的每个reducer必须有返回值,如果不做改变返回传入的state
  4. 使用combineReducers暴露出去之后,再使用store.getState()获取到的是这样一个结构,相当于每个reducer都有自己的一个state,不互通

    我们要获取到count的值 store.getState().change.count分别去取值

CombineReducers导致代码中断

CombineReducers导致代码中断

这是我在此处发布的主题之后的主题

经过大量的故障排除后,我发现此代码可以正常工作

import React from 'react';
import { createStore,combineReducers,applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import DataTableReducer from './reducers/DataTableReducer';
import DimensionPickerReducer from './reducers/DimensionPickerReducer';

const loggerMiddleware = createLogger();
const store = createStore(
    DimensionPickerReducer,applyMiddleware(
        thunkMiddleware,loggerMiddleware
    )
);

export default store;

但是如果我用合并减速器调用替换我的单个减速器

import React from 'react';
import { createStore,applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import DataTableReducer from './reducers/DataTableReducer';
import DimensionPickerReducer from './reducers/DimensionPickerReducer';

const loggerMiddleware = createLogger();
const store = createStore(
    combineReducers({
        DataTableReducer,DimensionPickerReducer
    }),loggerMiddleware
    )
);

export default store;

我立即开始因DimensionPicker控件出现错误,未指定必需的道具。

因此,CombineReducer方法对我不起作用。

我在这里上传了一个示例项目,它显示了问题。

https://github.com/abhitechdojo/MovieLensReact

您需要npm install在执行git clone之后运行

关于[Redux] redux之combineReducers的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于CombineReducers 源码解析、CombineReducers中的不变性、combineReducers使用、CombineReducers导致代码中断等相关知识的信息别忘了在本站进行查找喔。

本文标签: