React Hooks下的全局状态管理
React Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
动机
React没有提供将可复用性行为“附加”到组件的途径,以往的解决方案是高阶组件。但是这种方法需要重新组织组件结构,造成多层次的组件嵌套,使得代码难以理解。Hook使你在无需修改组件结构的情况下复用状态逻辑
复杂组件难以维护。组件的生命周期可能包含一些副作用与事件监听,而之后在
componentWillUnmount中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法组合在一起。Hook将相互关联的代码拆分成更小的函数(如useEffect)class中监听事件时常常会因为忘记绑定this造成意料之外的bug。Hook使得React在function组件中可以使用class的特性
解决方案
React Hooks + Redux
Redux
1 | // create |
2 | import { createStore } from 'redux'; |
3 | |
4 | function counterReducer(state = 0, action) { |
5 | switch (action.type) { |
6 | case 'INCREMENT': |
7 | return state + 1; |
8 | case 'DECREMENT': |
9 | return state - 1; |
10 | default: |
11 | return state; |
12 | } |
13 | } |
14 | let store = createStore(counterReducer); |
15 | |
16 | // use |
17 | // 可以手动订阅更新,也可以事件绑定到视图层。 |
18 | store.subscribe(() => |
19 | console.log(store.getState()) |
20 | ); |
21 | |
22 | // 改变内部 state 惟一方法是 dispatch 一个 action。 |
23 | // action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行 |
24 | store.dispatch({ type: 'INCREMENT' }); |
25 | // 1 |
26 | store.dispatch({ type: 'INCREMENT' }); |
27 | // 2 |
28 | store.dispatch({ type: 'DECREMENT' }); |
React-Redux
创建store,然后使用Provider组件包裹应用使得app能够访问store
1import React from 'react'2import ReactDOM from 'react-dom'34import { Provider } from 'react-redux'5import store from './store'67import App from './App'89const rootElement = document.getElementById('root')10ReactDOM.render(11<Provider store={store}>12<App />13</Provider>,14rootElement15)在需要使用store的地方connect组件
1import { connect } from 'react-redux'2import { increment, decrement, reset } from './actionCreators'34// const Counter = ...56const mapStateToProps = (state /*, ownProps*/) => {7return {8counter: state.counter9}10}1112const mapDispatchToProps = { increment, decrement, reset }1314export default connect(15mapStateToProps,16mapDispatchToProps17)(Counter)
React Hook Redux
React Redux自7.1版本之后引入了一系列Hook api用以支持React ook
同样需要先创建store,然后使用Provider组件包裹应用
在Function组件中只需要使用useSelector即可获取store中的值,其参数类似于connect中的mapStateToProps,使用useDispatch即可获取dispatch方法,改变状态值,使用useStore获取store的state
1function Test() {2const result = useSelector((state) => ({3counter: state,4}));5const dispatch = useDispatch();6return (7<div>8{result.counter}9<button onClick={()=>dispatch({ type: "INCREMENT" })}>+</button>10</div>11);12}1314export default Test;useSelector第二个参数可以用于自定义比较状态方法,默认比较方法为===
Context api
useContex + useReducer
- 手动创建一个Context,然后使用Context.Provider包裹应用,传递的store用useReducer包裹,在Function组件中使用useContext访问store的state和dispacth
1import React, { useReducer } from "react";2import Context from "./store/context";3import ContextTest from "./ContextTest";4import { counterReducer } from "./reducer";56function App() {7return (8<Context.Provider value={useReducer(counterReducer,0)}>9<ContextTest></ContextTest>10</Context.Provider>11);12}1314export default App;151617//ContextTest.jsx18import React, { useContext } from "react";19import Context from "./store/context";2021export default function ContextTest() {22const [state,dispatch] = useContext(Context);23return (24<div>25{state}26<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>27</div>28);29}
unstated
unstated
unstated是react社区中比较流行的轻量级的状态管理工具,在api设计的时候沿用react的设计思想,能够快速的理解和上手。
Unstated抛出三个对象,分别是Container、Subscribe和Provider。
创建store
1import { Container } from "unstated";23type CounterState = {4counter:number5}6class CounterContainer extends Container<CounterState>{7state = {8counter:09}1011increment() {12this.setState({13counter:this.state.counter+114})15}16}1718export default CounterContainer将应用包裹在unstated导出的Provider中
1import React from "react";2import { Provider } from "unstated";3import UnstatedTest from "./unstated/UnstatedTest";45function App() {6return (7<Provider>8<UnstatedTest></UnstatedTest>9</Provider>10);11}1213export default App;在需要使用全局store的子组件使用Subscribe包裹
1import React from 'react'2import { Subscribe } from 'unstated'3import CounterContainer from './CounterContainer.ts'45export default function UnstatedTest() {6return <Subscribe to={[CounterContainer]}>7{counter => (8<div>9{counter.state.counter}10<button onClick={()=>counter.increment()}>+</button>11</div>12)}13</Subscribe>14}
unstated-next
随着react hook的推行,unstated也推出了hook风格的api供使用,即unstated-next
创建container容器,这里需要使用hook来编写容器状态
1import { createContainer} from "unstated-next"2import { useState } from "react"34function useCustomHook() {5const [counter,setCounter] = useState(0)6const increment = ()=>setCounter(counter+1)7return {8counter,9increment10}11}1213export default createContainer(useCustomHook)使用创建的Container包裹应用
1import React, { useReducer } from "react";2import NextText from "./unstated-next/NextText";3import CounterContainer from "./unstated-next/CounterContainer";45function App() {6return (7<CounterContainer.Provider>8{/* <UnstatedTest></UnstatedTest> */}9<NextText></NextText>10</CounterContainer.Provider>11);12}1314export default App;15163. 在需要使用的Function组件中使用hook17```js18import React from 'react'19import { useContainer } from 'unstated-next'20import CounterContainer from './CounterContainer'2122export default function NextText() {23const store = useContainer(CounterContainer)24return <div>{store.counter}25<button onClick={()=>store.increment()}>+</button></div>26}
Mobx
mobx 将应用看成一个表格。对象、数组、原型、引用组成了引用的model;所有由state推出来的值(todo转换成的html,未完成的todo)都视作推导;model改变时,响应自动运行,这些函数不产生值,而是自动地执行一些任务;所有改变state的动作称为行动
Mobx
创建一个store
1import { observable } from "mobx";23class ObservableCounterStore {4@observable count = 0;5increment() {6this.count += 1;7}8}910export default new ObservableCounterStore();将应用包在Provider中
1import React from "react";2import MobxTest from "./mobx/MobxTest";3import { inject, Provider } from "mobx-react";4import CounterStore from "./mobx/CounterStore";56class App extends React.Component {7render() {8return (9<Provider counter={CounterStore}>10<MobxTest />11</Provider>12);13}14}1516export default App;需要响应mobx变量响应的组件用inject注入store,observer修饰,即可
1import React from 'react'2import { observer, inject } from 'mobx-react'345function MobxTest(props){6const {counter} = props7return <div>{counter.count}8<button onClick={() => counter.increment()}>+</button>9</div>10}1112export default inject('counter')(observer(MobxTest))
mobx-react-lite
mobx-react-lite提供了一种无需修饰即可引用mobx store的方法
创建store同上不再贴代码
使用React Context将创建的store包裹应用
1import React, { useReducer } from "react";2import storeContext from "./mobx-hook/storeContext"3import CounterStore from './mobx-hook/CounterStore'4import MobxHook from "./mobx-hook/MobxHook";56class App extends React.Component {7render() {8return (9<storeContext.Provider value={CounterStore}>10<MobxHook></MobxHook>11</storeContext.Provider>12);13}14}1516export default App;在需要使用store的子组件中用useContext获取store,用useObserver来监听observable变化
1import React, { useContext } from "react";2import { useObserver } from "mobx-react-lite";3import storeContext from "./storeContext";45export default function MobxHook() {6const store = useContext(storeContext);78return useObserver(() => (9<div>10<p>count: {store.count}</p>11<button onClick={()=>{store.count = store.count +1}}>+</button>12</div>13))14}
总结
大型项目Redux是第一选择,有较好的生态以及middleware,提供可预测的状态管理,但需要编写大量模版代码,且包体积在状态管理根据中较大,适合大型项目使用
context+hook提供了全局状态管理的基本功能,配合useReducer可以实现可预测的状态管理,且不用安装多余的包,适合在简单的项目中使用。但是用Context做Provider有一个问题,就是会导致所有使用到store的子组件产生不必要的渲染

unstated将状态和操作封装在container中,优点是在风格上比redux更贴近react的操作方式,也更容易上手,仅有200b,但是相比context+useReducer的方式没什么优势,除非原来就使用了unstated的项目,否则并不推荐。
mobx使用observable来管理状态,采用代理的方式,支持直接操作对象,相比redux来说更为简单灵活,但是太过灵活可能使得流程不那么清晰,适合中小项目使用