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
1
import React from 'react'
2
import ReactDOM from 'react-dom'
3
4
import { Provider } from 'react-redux'
5
import store from './store'
6
7
import App from './App'
8
9
const rootElement = document.getElementById('root')
10
ReactDOM.render(
11
<Provider store={store}>
12
<App />
13
</Provider>,
14
rootElement
15
)
在需要使用store的地方connect组件
1
import { connect } from 'react-redux'
2
import { increment, decrement, reset } from './actionCreators'
3
4
// const Counter = ...
5
6
const mapStateToProps = (state /*, ownProps*/) => {
7
return {
8
counter: state.counter
9
}
10
}
11
12
const mapDispatchToProps = { increment, decrement, reset }
13
14
export default connect(
15
mapStateToProps,
16
mapDispatchToProps
17
)(Counter)
React Hook Redux
React Redux自7.1版本之后引入了一系列Hook api用以支持React ook
同样需要先创建store,然后使用Provider组件包裹应用
在Function组件中只需要使用useSelector即可获取store中的值,其参数类似于connect中的mapStateToProps,使用useDispatch即可获取dispatch方法,改变状态值,使用useStore获取store的state
1
function Test() {
2
const result = useSelector((state) => ({
3
counter: state,
4
}));
5
const dispatch = useDispatch();
6
return (
7
<div>
8
{result.counter}
9
<button onClick={()=>dispatch({ type: "INCREMENT" })}>+</button>
10
</div>
11
);
12
}
13
14
export default Test;
useSelector第二个参数可以用于自定义比较状态方法,默认比较方法为===
Context api
useContex + useReducer
- 手动创建一个Context,然后使用Context.Provider包裹应用,传递的store用useReducer包裹,在Function组件中使用useContext访问store的state和dispacth
1
import React, { useReducer } from "react";
2
import Context from "./store/context";
3
import ContextTest from "./ContextTest";
4
import { counterReducer } from "./reducer";
5
6
function App() {
7
return (
8
<Context.Provider value={useReducer(counterReducer,0)}>
9
<ContextTest></ContextTest>
10
</Context.Provider>
11
);
12
}
13
14
export default App;
15
16
17
//ContextTest.jsx
18
import React, { useContext } from "react";
19
import Context from "./store/context";
20
21
export default function ContextTest() {
22
const [state,dispatch] = useContext(Context);
23
return (
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
1
import { Container } from "unstated";
2
3
type CounterState = {
4
counter:number
5
}
6
class CounterContainer extends Container<CounterState>{
7
state = {
8
counter:0
9
}
10
11
increment() {
12
this.setState({
13
counter:this.state.counter+1
14
})
15
}
16
}
17
18
export default CounterContainer
将应用包裹在unstated导出的Provider中
1
import React from "react";
2
import { Provider } from "unstated";
3
import UnstatedTest from "./unstated/UnstatedTest";
4
5
function App() {
6
return (
7
<Provider>
8
<UnstatedTest></UnstatedTest>
9
</Provider>
10
);
11
}
12
13
export default App;
在需要使用全局store的子组件使用Subscribe包裹
1
import React from 'react'
2
import { Subscribe } from 'unstated'
3
import CounterContainer from './CounterContainer.ts'
4
5
export default function UnstatedTest() {
6
return <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来编写容器状态
1
import { createContainer} from "unstated-next"
2
import { useState } from "react"
3
4
function useCustomHook() {
5
const [counter,setCounter] = useState(0)
6
const increment = ()=>setCounter(counter+1)
7
return {
8
counter,
9
increment
10
}
11
}
12
13
export default createContainer(useCustomHook)
使用创建的Container包裹应用
1
import React, { useReducer } from "react";
2
import NextText from "./unstated-next/NextText";
3
import CounterContainer from "./unstated-next/CounterContainer";
4
5
function App() {
6
return (
7
<CounterContainer.Provider>
8
{/* <UnstatedTest></UnstatedTest> */}
9
<NextText></NextText>
10
</CounterContainer.Provider>
11
);
12
}
13
14
export default App;
15
16
3. 在需要使用的Function组件中使用hook
17
```js
18
import React from 'react'
19
import { useContainer } from 'unstated-next'
20
import CounterContainer from './CounterContainer'
21
22
export default function NextText() {
23
const store = useContainer(CounterContainer)
24
return <div>{store.counter}
25
<button onClick={()=>store.increment()}>+</button></div>
26
}
Mobx
mobx 将应用看成一个表格。对象、数组、原型、引用组成了引用的model;所有由state推出来的值(todo转换成的html,未完成的todo)都视作推导;model改变时,响应自动运行,这些函数不产生值,而是自动地执行一些任务;所有改变state的动作称为行动
Mobx
创建一个store
1
import { observable } from "mobx";
2
3
class ObservableCounterStore {
4
@observable count = 0;
5
increment() {
6
this.count += 1;
7
}
8
}
9
10
export default new ObservableCounterStore();
将应用包在Provider中
1
import React from "react";
2
import MobxTest from "./mobx/MobxTest";
3
import { inject, Provider } from "mobx-react";
4
import CounterStore from "./mobx/CounterStore";
5
6
class App extends React.Component {
7
render() {
8
return (
9
<Provider counter={CounterStore}>
10
<MobxTest />
11
</Provider>
12
);
13
}
14
}
15
16
export default App;
需要响应mobx变量响应的组件用inject注入store,observer修饰,即可
1
import React from 'react'
2
import { observer, inject } from 'mobx-react'
3
4
5
function MobxTest(props){
6
const {counter} = props
7
return <div>{counter.count}
8
<button onClick={() => counter.increment()}>+</button>
9
</div>
10
}
11
12
export default inject('counter')(observer(MobxTest))
mobx-react-lite
mobx-react-lite提供了一种无需修饰即可引用mobx store的方法
创建store同上不再贴代码
使用React Context将创建的store包裹应用
1
import React, { useReducer } from "react";
2
import storeContext from "./mobx-hook/storeContext"
3
import CounterStore from './mobx-hook/CounterStore'
4
import MobxHook from "./mobx-hook/MobxHook";
5
6
class App extends React.Component {
7
render() {
8
return (
9
<storeContext.Provider value={CounterStore}>
10
<MobxHook></MobxHook>
11
</storeContext.Provider>
12
);
13
}
14
}
15
16
export default App;
在需要使用store的子组件中用useContext获取store,用useObserver来监听observable变化
1
import React, { useContext } from "react";
2
import { useObserver } from "mobx-react-lite";
3
import storeContext from "./storeContext";
4
5
export default function MobxHook() {
6
const store = useContext(storeContext);
7
8
return 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来说更为简单灵活,但是太过灵活可能使得流程不那么清晰,适合中小项目使用