前言
本文将介绍 Reducer 的拆分。
正文
Reducer 函数负责生成 State。但由于整个 Web 应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。
为此,我们给自己加个需求,来讲述 Reducer 的拆分。
如下图,在原有 state 只存储 count 计数的基础上,增加一个 status 来存储登录状态。
只要在原有基础上做简单修改,即可满足需求,但我们的目的不在此。
- 在
reducer
函数上增加对LOGIN_IN
、LOGIN_OUT
的处理:
// reducers/index.js const reducer = (prevState, action) => { const { type, payload } = action switch (type) { case 'ADD': return { ...prevState, count: prevState.count + payload } case 'SUB': return { ...prevState, count: prevState.count - payload } case 'LOGIN_IN': return { ...prevState, status: 'online' } case 'LOGIN_OUT': return { ...prevState, status: 'offline' } default: // default 或者未知 action 时,返回旧的 state return prevState } } export default reducer
- 在
Home
组件上,增加一个dispatch
分发Action
的动作。
import React, { Component } from 'react' import { connect } from 'react-redux'; import store from '../../store' class Home extends Component { constructor(props) { super(props) this.state = {} } handle(type, val) { this.props.simpleDispatch(type, val) // 获取 State 快照 console.log(`当前操作是 ${type},count 为:${store.getState().count}`) } render() { return ( <div> <h3>Home Component!</h3> {/* 将 state 展示到页面上 */} <h5>count:{this.props.count}</h5> <button onClick={this.handle.bind(this, 'ADD', 1)}>加一</button> <button onClick={this.handle.bind(this, 'SUB', 1)}>减一</button> <h5>status:{this.props.status}</h5> <button onClick={() => {this.props.toggleStatus('LOGIN_IN')}}>Login in</button> <button onClick={() => {this.props.toggleStatus('LOGIN_OUT')}}>Login out</button> </div> ) } } const mapStateToProps = (state, ownProps) => { return { count: state.count, status: state.status } } const mapDispatchToProps = (dispatch, ownProps) => { return { simpleDispatch: (type, payload) => { dispatch({ type, payload }) }, toggleStatus: type => { dispatch({ type }) } } } export default connect(mapStateToProps, mapDispatchToProps)(Home)
如果对此前的文章介绍的内容已经掌握了的话,这些已经是轻而易举的了。
但是,我们会发现一个问题,上面的 reducer 函数夹杂着对 count、status 的处理,两种截然不同的 Action 并不会干扰双方的 State ,它们之间没有关联。如果按照上述将其写在一起,若后续增加更多的存储状态时,它看起来就更加的紊乱,维护成本也将越来越高。
所以,我们需要将其进行拆分。
那么如何拆分呢?
我们试图将上述的 reducer
函数拆分成两个 countReducer
、statusReducer
(如下),这样一拆,Reducer 就易读很多了,两者职能也很清晰。
// count reducer const countReducer = (prevState, action) => { const { type, payload } = action switch (type) { case 'ADD': return { ...prevState, count: prevState.count + payload } case 'SUB': return { ...prevState, count: prevState.count - payload } default: return prevState } } // status reducer const statusReducer = (prevState, action) => { const { type } = action switch (type) { case 'LOGIN_IN': return { ...prevState, status: 'online' } case 'LOGIN_OUT': return { ...prevState, status: 'offline' } default: return prevState } }
然后我们现在的问题是,如何将其合成一个标准的 Reducer 函数?
combineReducers(reducers) 相关概念
Redux 提供了一个 combineReducers
方法,它的作用是,把一个由多个不同 reducer
函数作为 value
值的对象,合并成一个最终的 reducer
函数,然后就可以对这个 reducer
调用 createStore
方法。
合并后的 reducer
可以调用各个子 reducer
函数,并把它们返回的结果合并成一个 state
对象。由 combineReducers()
返回的 state
对象,会将传入的每个 reducer
返回的 state
按其传递给 combineReducers()
时对应的 key
进行命名。
举个例子:
const rootReducer = combineReducers({ keyA: aReducer, keyB: bReducer }) // 那么返回的 state 对象就是:{ keyA: ..., keyB: ... }
*还有几乎所有关于 combineReducers
的教程都有提到的一点:使用 ES6 对象属性简写的方式使得其看起来更简洁。这里不再赘述,太简单了,相信你们早就掌握了。
它的返回值看这里(为了避免被这些概念绕晕,个人建议不看)。我们只要知道
combineReducers()
的返回值可直接作为createStore()
第一个参数使用即可。
需要注意的是
本函数设计的时候有点偏主观,就是为了避免新手犯一些常见错误。也因此作者故意设定一些规则。(但如果你自己手动编写 rootRedcuer 时并不需要遵守这些规则。)
每个传入 combineReducers
的 reducer
都需满足以下规则:
- 所有未匹配到的
action
,必须把它接收到的第一个参数也就是那个state
原封不动返回。- 永远不能返回
undefined
。当过早return
时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时combineReducers
会抛异常。- 如果传入的
state
就是undefined
,一定要返回对应reducer
的初始state
。根据上一条规则,初始state
禁止使用undefined
。使用 ES6 的默认参数值语法来设置初始state
很容易,但你也可以手动检查第一个参数是否为undefined
。
虽然 combineReducers
自动帮你检查 reducer
是否符合以上规则,但你也应该牢记,并尽量遵守。即使你通过 Redux.createStore(combineReducers(...), initialState)
指定初始 state
,combineReducers
也会尝试通过传递 undefined
的 state
来检测你的 reducer
是否符合规则。因此,即使你在代码中不打算实际接收值为 undefined
的 state
,也必须保证你的 reducer
在接收到 undefined
时能够正常工作。
了解相关规则之后
对我们的项目做相应的调整,以分拆 Reducer。
- 首先将
countReducer
、statusReducer
分别分离到/reducers/countReducer.js
、/reducers/statusReducer.js
两个文件中。
// reducers/countReducer.js const countReducer = (prevState = {}, action) => { const { type, payload } = action switch (type) { case 'ADD': return prevState + payload case 'SUB': return prevState - payload default: return prevState } } export default countReducer
// reducers/statusReducer.js const statusReducer = (prevState = {}, action) => { const { type } = action switch (type) { case 'LOGIN_IN': return 'online' case 'LOGIN_OUT': return 'offline' default: return prevState } } export default statusReducer
- 引入
combineReducers
// reducers/index.js import { combineReducers } from 'redux' import countReducer from './countReducer' import statusReducer from './statusReducer' const rootReducer = combineReducers({ count: countReducer, status: statusReducer }) export default rootReducer
至此我们的 Reducer 就拆分成功了,效果图就不贴了,与拆分之前无异。
当然了,以上教程只是为了引入而引入。如果已经掌握了,那么实际项目中遇到再复杂的 store
状态存储我们都不用怕了。