Reducer

Reducer 是一个纯函数,接收旧 State 和 Action,返回新的 State,即(previousState, action) => newState。纯函数的意义在于相同的输入只会得到相同的输出,并且不会产生任何可观察的副作用,所以以下操作永远不要在 Reducer 中进行:

  • 修改传入的参数;
  • 执行有副作用的操作,例如执行 API 请求或者路由跳转。
  • 调用其他非纯函数,例如即Date.now()或者即Math.random()

Redux 在首次执行时,State 为undefined,此时可以返回应用的初始 State。以下是一个 Reducer 的示例。

function contentApp(state = initialState, action) {
  switch (action.type) {
    case ADD_CONTENT:
      return Object.assign({}, state, { content: action.payload.content });
    case SET_FILTER:
      return Object.assign({}, state, { filter: action.payload.filter });
    default:
      return state;
  }
}

示例中使用Object.assign()创建了一个 State 的副本,并且在遇到未知的 Action 时一定要返回旧的 State。按照示例中的写法,当 Action 逐渐增多,整个 Reducer 将会越变越大,这时就需要考虑拆分 Reducer。Reducer 的拆分就必然意味着 Reducer 的合并。使用一个函数来作为主 Reducer,调用若干子 Reducer 来分别处理 State 中的一部分数据,然后再将这些数据合并为一个大的单一对象,这就是开发 Redux 应用最基础的模式。在这种模式下,主 Reducer 不需要设置初始化时完整的 State,各个子 Reducer 会返回各自的初始默认值。

Tip

在 ES7 标准中,可以使用对象展开运算符来替代Object.assign()。上例中的Object.assign()可以改写为return { ...state, content: action.payload.content }

将以上示例改为拆分的写法如下。

function filter(state = initialState, action) {
  switch (action.type) {
    case SET_FILTER:
      return Object.assign({}, state, { filter: action.payload.filter });
    default:
      return state;
  }
}

function add(state = [], action) {
  switch (action.type) {
    case ADD_CONTENT:
      return Object.assign({}, state, { content: action.payload.content });
    default:
      return state;
  }
}

// 主Reducer
function contentApp(state = {}, action) {
  return {
    filter: filter(state.filter, action),
    add: add(state.content, action)
  };
}

在这种模式下,随着应用规模的膨胀,拆分后的 Reducer 只需要放置在不同的文件中,保持其独立性即可。针对主 Reducer,Redux 提供了combineReducers()工具来完成示例中主 Reducer 的事情。所以上例中主 Reducer 可以改写为以下形式。

import { combineReducers } from 'redux';

const contentApp = combineReducers({
  filter,
  content: add
});

或者还可以为其中不同的 Reducer 设置不同的键值,以定义其要更改的 State。

Tip

在使用 ES6 语法时,可以将所有的顶级 Reducer 都放置在一个独立文件中,并使用export暴露出来,在合成 Reducer 的文件中使用import * as reducers from './fileName'以 Object 方式导入全部 Reducer,所获得的这个 Object 可以直接给予combineReducers()来合成 Reducer。