思考React状态管理
# 一、前言
目前在项目中有几种状态管理方案:
自定义Hook
自定义Hook
+useContext
redux
与useReducer
# 二、常见的几种状态管理方案
# 1.自定义Hook
在记账项目中,直接使用的自定义useHooks
函数,如下:
const useTags = () = >{
const [tags, setTags] = useState<{ id: number; name: string }[]>([])
// 增
const addTag = () => {
const tagName = window.prompt("请输入tag名称")
.....
}
// 删
const deleteTag = (id) =>{}
// 改
const updateTag = (id, obj) => {}
// 查
const findTag = (id) => tags.filter(tag => tag.id === id)[0]
// 自定义导出的函数
return { tags, addTag, setTags, findTag, updateTag, deleteTag }
}
export { useTags }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在使用的时候:
// 需要运行一下useTags才可以析构出对应的函数和方法
const {tags,addTag} = useTags()
2
注意:在一个大型的项目中,需要保证
useTags
只调用了一次,毕竟它只是一个函数,每次调用都会创建新的state,如果在不同的组件中多次调用这会导致数据不同步。
那么如何做到只调用一次useTags
呢,可以使用useContext
在最上顶成组件中将useTags
执行后返回的对象(操作tags
的方法)传递到子组件中即可。
# 2.自定义Hooks+useContext
context.tsx
import {createContext} from 'React'
import {RecordAction} from 'hooks/useRecord' //createContext接受的类型
const ContextReacrd = createContext<RecordAction>({})
export default ContextRecord
2
3
4
App.tsx
import React from 'react'
import ContextRecord from '/../ContextRecord'
import useRecord from 'hooks/useRecord'
const App = ()=>{
const recordAction = useRecord() //返回操作record的方法
return(
<ContextRecord.Provider value={recordAction}>
<Child />
</ContextRecord.Provider>
)
}
2
3
4
5
6
7
8
9
10
11
这样在后代组件中就可以使用useContext
来拿到操作state
的函数。
Child.tsx
import React,{useContext} from 'react'
import ContextRecord from '/../ContextRecord'
const Child = ()=>{
const recordAction = useContext(ContextRecord)//拿到操作record的函数
return (
<Child2 />
)
}
export default Child
2
3
4
5
6
7
8
9
10
# 3.useState与setState的区别
setState 和 useReducer 的功能很类似,都是状态管理,理论上他们两个的功能是用另一个可以代替的。为什么 React 要提供这样两个功能有重叠的 API 呢?
useState
提供了更加细粒度的状态管理。
个类组件只有一个 setState
,但是一个函数组件却可以有很多 useState
,这让我们可以把独立的状态分开管理,逻辑上更清晰了,更方便维护了,这是 hooks
的天然优势。
# 4.useReducer:低成本的数据流
主要参考文章[3]
简介:简易版Redux
虽说 useReducer
可以拿来做组件内状态管理,但是 useReducer
对于单组件来说太重了,绝大多数情况下是用用不到的。
useReducer
更适合拿来做简单场景下的数据流。useReducer
是阉割版的 redux,只缺了一个状态共享能力,用 hooks 的 useContext
刚刚好。
# 三、总结
项目开发到后期一块很大的维护成本就是大组件,大组件一般都是逻辑复杂、逻辑和 UI 没接耦。React 推荐的开发方式和逻辑和 UI 分离,类组件时期实现的方式是通过 container 管理数据,UI 组件只负责展示,但在紧张的项目迭代中要时刻做到这一点是很麻烦的,原因是类组件承载业务逻辑的最小单元是组件,这个粒度太大了。
Hooks 提供了一种更加自然的方式。因为函数组件本身就是一个返回值比较特殊的函数,函数比类要灵活的多,那我们可以很方便的把某个业务逻辑单独封装在某一个函数中,这种逻辑、UI 接耦的方式简直浑然天成。
这里就要说到 useState 为什么要细粒度的使用,其实从它的 API 也可以看出来,为什么 useState 返回的是一个数组而不是一个对象?因为可以重命名。为什么要重命名?因为需要有明确的语义。什么时候需要明确的语义?当这个值的含义很具体的时候。可以看出 React 希望开发者使用 useState 管理的状态都是原子级的,这样有什么好处呢?为了逻辑解耦啊。否则把一大坨逻辑抽到另一个函数,和放在组件里并没有什么区别。
useState 算是组件内状态管理一个非常优雅的解决方案,但是组件间状态管理的问题还没有解决。Redux 是比较符合 React 理念的数据流管理工具(单向数据流 (opens new window)、易追踪、严格 immutable),而且它和 useContext 结合起来简直就是天衣无缝,React 吸收了 redux 的思想,也算是补齐了在数据流上的能力。
可惜 Redux 适合管理大型应用的复杂数据流,简单场景用着总有点头重脚轻的感觉,useReducer 当然也有同样的问题,他虽然比 redux 轻量,可以做组件内状态管理,但是大多数情况用着会头重脚轻的,除非万不得已,否则我并不想看到 reducer 那坨模板代码。但是拿来做简易的组件间数据流管理就很合适,Hooks 在这点上的能力比类组件要强大很多。