首页 男生 其他 深入浅出React和Redux

4.5.3 组合reducer

深入浅出React和Redux 程墨 6335 2021-04-06 02:29

  您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!

  

  4.5.3 组合reducer

  在todos和filter目录下,各有一个reducer.js文件定义两个功能模块的reducer。

  对于reducer我们并不陌生,在第3章的ControlPanel例子中我们就创建过reducer。但是在那个例子中,整个应用只有一个reducer。而在Todo应用中,两个功能模块都有自己的reducer,而Redux的createStore函数只能接受一个reducer,那么怎么办?

  这是Redux最有意思的一部分,虽然Redux的createStore只接受一个reducer,却可以把多个reducer组合起来,成为一体,然后就可以被createStore函数接受。

  在src/Store.js文件中,我们完成了reducer的组合,代码如下:

  import {createStore, combineReducers} from 'redux';

  import {reducer as todoReducer} from './todos';

  import {reducer as filterReducer} from './filter';

  const reducer = combineReducers({

  todos: todoReducer,

  filter: filterReducer

  });

  export default createStore(reducer);

  我们使用了Redux提供的一个函数combineReducers来把多个reducer函数合成为一个reducer函数。

  combineReducers函数接受一个对象作为参数,参数对象的每个字段名对应了State状态上的字段名(在上面的例子中字段名分别是todos和filter),每个字段的值都是一个reducer函数(在上面的例子中分别是todoReducer和filterReducer),combineReducers函数返回一个新的reducer函数,当这个新的reducer函数被执行时,会把传入的state参数对象拆开处理,todo字段下的子状态交给todoReducer,filter字段下的子状态交给filter-Reducer,然后再把这两个调用的返回结果合并成一个新的state,作为整体reducer函数的返回结果。

  假设,当前State上的状态可以用currentState代表,这时候给Store派发一个action对象,combineReducers产生的这个reducer函数就会被调用,调用参数state就是current-State。这个reducer函数会分别调用todoReducer和filterReducer,不过传递过去的state参数有些变化,调用todoReducer的参数state值是currentState.todos,调用filter-Reducer的state是currentState.filter,当todoReducer和filterReducer这两个函数返回结果之后,combineReducers产生的reducer函数就用这两个结果分别去更新Store上的todos和filter字段。

  所以,现在我们来看功能模块的reducer,会发现state的值不是Redux上那个完整的状态,而是状态上对应自己的那一部分。

  在src/todos/reducer.js中可以看到,state参数对应的是Store上todos字段的值,默认值是一个数组,reducer函数往往就是一个以action.type为条件的switch语句构成,代码模式如下:

  import {ADD_TODO, TOGGLE_TODO, REMOVE_TODO}from './actionTypes.js';

  export default (state = [], action) => {

  switch(action.type) {

  //针对action.type所有可能值的case语句

  }

  }

  先看对于ADD_TODO这种action类型的处理,代码如下:

  case ADD_TODO: {

  return [

  {

  id: action.id,

  text: action.text,

  completed: false

  },

  ...state

  ]

  }

  在这里,我们使用了ES6的扩展操作符来简化reducer的代码,扩展操作符可以用来扩展一个对象,也可以用来扩展一个数组。

  现在state是一个数组,我们想要返回一个增加了一个对象的数组,就这样写:

  return [newObject, ...state];

  为什么我们不直接使用熟悉的数组push或者unshift操作呢?

  绝对不能,因为push和unshift会改变原来那个数组,还记得吗?reducer必须是一个纯函数,纯函数不能有任何副作用,包括不能修改参数对象。

  对于TOGGLE_TODO这种action类型的处理,代码如下:

  case TOGGLE_TODO: {

  return state.map((todoItem) => {

  if (todoItem.id === action.id) {

  return {...todoItem, completed: !todoItem.completed};

  } else {

  return todoItem;

  }

  })

  }

  扩展操作符可以在一对{}符号中把一个对象展开,这样,在{}中后面的部分的字段值,可以覆盖展开的部分:

  return {...todoItem, completed: !todoItem.completed};

  像上面的代码中,返回了一个新的对象,所有字段都和todoItem一样,只是completed字段和todoItem中的completed布尔类型值正好相反。

  对于REMOVE_TODO这种action类型的处理,代码如下:

  case REMOVE_TODO: {

  return state.filter((todoItem) => {

  return todoItem.id !== action.id;

  })

  }

  对于删除操作,我们使用数组的filter方法,将id匹配的待办事项过滤掉,产生了一个新的数组。

  最后,reducer中的switch语句一定不要漏掉了default的条件,代码如下:

  default: {

  return state;

  }

  因为reducer函数会接收到任意action,包括它根本不感兴趣的action,这样就会执行default中的语句,应该将state原样返回,表示不需要更改state。

  在src/filter/reducer.js中定义了filter模块的reducer,代码如下:

  import {SET_FILTER} from './actionTypes.js';

  import {FilterTypes} from '../constants.js'

  export default (state = FilterTypes.ALL, action) => {

  switch(action.type) {

  case SET_FILTER: {

  return action.filter;

  }

  default:

  return state;

  }

  }

  这个reducer更加简单,所做的就是把Redux Store上filter字段的值设为action对象上的filter值。

  我们来总结一下Redux的组合reducer功能,利用combineReducers可以把多个只针对局部状态的“小的”reducer合并为一个操纵整个状态树的“大的“reducer,更妙的是,没有两个“小的”reducer会发生冲突,因为无论怎么组合,状态树上一个子状态都只会被一个reducer处理,Redux就是用这种方法隔绝了各个模块。

  很明显,无论我们有多少“小的”reducer,也无论如何组合,都不用在乎它们被因为调用的顺序,因为调用顺序和结果没有关系。 深入浅出React和Redux

目录
设置
手机
书架
书页
评论