从零到一搭建 react 项目系列之(十)

简介: 本文将介绍 Reducer 的拆分。

前言


本文将介绍 Reducer 的拆分。


正文


Reducer 函数负责生成 State。但由于整个 Web 应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。


为此,我们给自己加个需求,来讲述 Reducer 的拆分。

如下图,在原有 state 只存储 count 计数的基础上,增加一个 status 来存储登录状态。

43.webp.jpg

只要在原有基础上做简单修改,即可满足需求,但我们的目的不在此。

  1. reducer 函数上增加对 LOGIN_INLOGIN_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


  1. 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 函数拆分成两个 countReducerstatusReducer(如下),这样一拆,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 时并不需要遵守这些规则。)

每个传入 combineReducersreducer 都需满足以下规则:


  • 所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。
  • 永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。
  • 如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为undefined


虽然 combineReducers 自动帮你检查 reducer 是否符合以上规则,但你也应该牢记,并尽量遵守。即使你通过 Redux.createStore(combineReducers(...), initialState) 指定初始 statecombineReducers 也会尝试通过传递 undefinedstate 来检测你的 reducer 是否符合规则。因此,即使你在代码中不打算实际接收值为 undefinedstate,也必须保证你的 reducer 在接收到 undefined 时能够正常工作。


了解相关规则之后


对我们的项目做相应的调整,以分拆 Reducer。

  1. 首先将 countReducerstatusReducer 分别分离到 /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


  1. 引入 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 状态存储我们都不用怕了。

目录
相关文章
|
6月前
|
前端开发 JavaScript 测试技术
从零开始搭建react+typescript+antd+redux+less+vw自适应项目
从零开始搭建react+typescript+antd+redux+less+vw自适应项目
178 0
|
6月前
|
前端开发 API 数据安全/隐私保护
【第45期】一文解决React项目的权限管理
【第45期】一文解决React项目的权限管理
331 0
|
6月前
|
Web App开发 资源调度 JavaScript
竟然可以在一个项目中混用 Vue 和 React?
竟然可以在一个项目中混用 Vue 和 React?
646 0
|
6月前
|
运维 JavaScript 前端开发
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
|
6月前
|
存储 JSON 前端开发
react保姆级搭建新项目
此文主要以ts+vite+router6+antd 快速搭建一个react项目,适用于初学者,用于学习交流
106 2
|
6月前
|
移动开发 JavaScript 前端开发
vue/react项目刷新页面出现404的原因以及解决办法
vue/react项目刷新页面出现404的原因以及解决办法
680 0
|
6月前
|
存储 资源调度 前端开发
【React | 完整项目创建流程】能看到这么详细的React配置流程,就偷着乐吧!
【React | 完整项目创建流程】能看到这么详细的React配置流程,就偷着乐吧!
211 1
|
6月前
|
前端开发 JavaScript API
Github 上 8 个很棒的 React 项目
Github 上 8 个很棒的 React 项目
1443 0
|
6月前
|
前端开发 开发工具 git
React项目包结构的作用
React项目包结构的作用
113 0
|
6月前
|
前端开发
React 中 react-i18next 切换语言( 项目国际化 )
React 中 react-i18next 切换语言( 项目国际化 )
589 0