你不需要Redux
Redux 是一个知名度和使用率比较高的状态管理库,取『你不需要 Redux』这个标题应该理解为『在大部分场景下,你不需要使用 Redux 做状态管理』。所以本篇将基于 React,不用其他框架或库来实现一个简单的状态管理方案。
在没有写这篇文章之前,你问我何时应该使用 Redux,我可能会侃侃而谈,而现在我竟不知道从哪里开始回答这个问题。在之前我会把 Redux 用来做全局状态管理,当我被问道这个问题的时候,我会拿『应该在何时使用全局状态管理?』这个问题的答案来回答。而这个问题实际上应该是,在做全局状态管理的时候,什么时候应该使用 Redux?
我不能给出正确的答案,也许你可以去 Redux 的网站上去看看,我是一个比较懒的人,懒得学,看到 Redux 的文档就头疼。于是就自己去简单实现一个全局状态管理的功能(为简化写法,下文使用状态管理来指代全局状态管理),整个代码只有 30 行左右。
一个简易的状态管理
这个状态管理有一些小的缺陷:
- 只能在 React 中使用
- React 版本需要 ≥ 16.8
- 只有简单的状态管理功能
设计实现
Redux 状态管理到底做了什么呢?
- 保存状态
- 当状态改变时触发视图更新
- 状态的增删改
貌似就是这些事了吧,那就围绕这三个功能来实现。
状态保存
状态是某一时刻变量的集合,保存状态非常简单,只需要将值放在一个集合中就行,所以说,可以直接将需要管理的状态放在一个Map或者一个Object中。
状态更新视图
React 和 Vue 都是数据驱动的,通常都是通过数据去更新视图,所以一个全局状态管理是必须要能够更新视图的,在本篇文章中,直接借助 React 更新机制来更新视图
React 视图更新有 3 种方式:
- 组件内 State 改变
- 组件接收的 Prop 改变
- 组件消费的 Context 改变
单纯地靠 State 或者 Props 改变是不能达到改变整个 React 应用状态的的效果的,二者需要结合使用,一种常规做法是将状态存放在顶级组件的state中,通过props一层一层地向下传递,这样写显然是非常麻烦的,而且一旦组件层级发生改变,很有可能整个子组件树都要修改。
好在 Context 帮我们解决了这个问题,它提供了一种在组件之间共享此类值的方式,在 Context 下,你可以在任意组件内使用其存放的 value,不用显式地通过props传值。
状态数据更新
在任意组件中改变 Context 的 value,必须要保证这个 value 是响应式的,如果是普通的变量,在别的地方更新这个 value,Context 是不会更新的,所以将 state 作为 Context 中的 value,想要在其他组件改变 value,只需要将更新 state 的函数一起放在 Context 中即可,在其他组件中调用这个函数,state 随着 value 更新,这样就会触发 Context 更新了。
代码实现
根据以上分析,这个状态管理是基于 Context 和 State 的,所以先来创建一个组件,而且要让其他组件都被 Context 包裹起来,组件的结构大致这样:
1 | import React, { useState, createContext } from "react"; |
核心代码就是这些了,只不过还需要一些优化。
Action&Reducer
就把需要全局使用的状态都放在 store 中,总觉得有什么东西混在一团,不便于管理,于是还需要将更新的逻辑抽象一下,变得更清晰,其实也是参照 Redux 哲学,写一个更像 Redux 的状态管理。
好在React hooks已经帮我们做了大部分工作,比如一个和 useState 相似的 useReducerhooks ,相信使用过 Redux的开发者用起来会非常趁手。useReducer 接收三个参数,分别是一个 Reducer,一个初始化值,一个惰性初始化函数。
主要说一下 Reducer,它是一个函数,接收一个 State 和,一个 Action。Action 是一个纯对象,充当信使的作用,按照 Redux 中的说法,Action 是一个包含 type 字段的对象,在这里没有限制,但还是按照大家熟悉的 Redux 来写吧。
将 Action 定义成这样的结构:
1 | interface Action { |
为了方便管理,将可使用的 Action 放在 actions.type.json 文件中,这里我写一个比较特殊的 Action:
1 | { "SET_DATA": "setData" } |
这个 Action 对应的功能如 4~6 行所示:
1 | import { SET_DATA } from "./actions.type.json"; |
这个 Action 可以直接改变或者新增某个属性值,基本上能够在更新状态的绝大多数场景下使用。
将这些 Action 对应的函数放在一个对象中,相当于 Redux 的 combineReducers,这样写也可以避免使用 if或者 switch 语句来选择执行某个函数。
现在可以将 useState 替换成 useReducer 了:
1 | // reducer |
如何使用
其用法和 Redux 大体相同,为了避免 Context 被重新渲染,应尽量将其作为顶层组件:
1 | ReactDom.render( |
读取状态
1 | import React, { useContext } from "react"; |
更新状态
1 | import React, {useContext} from "react" |
最后
这个状态管理仿造 Redux 的用法简单实现出来了,用于简单的全局状态存储已经足够,不用去使用 redux 这个笨重的库,我猜绝大多数的项目都不需要去使用 Redux,如果想使用中间件,异步更新状态等特性,采用 Redux 或许是更好的选择。
本文实现的完整代码以及使用示例已经放在了此处 ,文中有错误的地方欢迎指正。

