React Hooks useContext + useReducer实现简易Redux
context api是简化版的redux,他没有redux强大生态体系,结合各自中间件例如thunk或saga,做data fetching或处理side effect,不过单单想存一些share data避免props drilling的问题却绰绰有余。
- context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
- reducer 应是纯函数,根据旧的状态和新的参数计算出最新的状态,其中新的参数来自于 dispatch(新的参数)
所以使用 context 还是 redux 要看需求、看成本、看以后拓展性、可维护性。(react-redux 从 v7.1.0开始也提供了 hooks api 以减少繁琐的高阶组件嵌套)
跟着 React 官网文档,首先去看 高级指引-Context,重点理解 React.createContext(initialValue)
方法返回的 Provider 和 Consumer 组件,这是生产者和消费者模型。
知道 context 在函数式组件中基本用法后,去看 useContext,重点去感受使用了 useContext 后,代替了之前 <MyContext.Consumer>
这种写法,令 Context 的引用变得更加方便。
接下来假设你本身已经使用过 Redux,对 Redux 那一套流程有基本的了解,Redux 本身是可以脱离 React 运行的,这是个用于全局状态管理的库,你当然可以结合 JQuery 来使用,但目前来看,之所以这么流行,还是因为结合了 React,所以 Redux 在 React 中主要依赖了 react-redux 这个库。
跟着 React 官方文档,了解 useReducer 的用法,这里也提供了 useReducer 的大概原理。
相信我,尽管可能先从其他地方开始学习 hooks,但是官方文档绝对不要错过。
我们模拟类似 redux 的功能,简单分为三步:
- 使用 useReducer 在根组件创建需要共享的 state 和用来更新它的 dispatch()
- 使用 context api 把刚才的 state 和 dispatch 同时共享下去
- 使用 useContext 方便底层组件读写共享的 state
import React, { useContext, useReducer } from 'react';
const BankContext = React.createContext({});
// 和 redux 一样,综合根据旧 state 和 dispatch 而来的数据,计算出最新的 state
function reducer(state, action) {
switch (action.type) {
case 'deposit':
return { balance: state.balance + action.payload };
default:
throw new Error();
}
}
// 纯粹为了展示 useReducer 第三个参数
function init(initialCount) {
return { balance: initialCount };
}
// 根组件
export default function App() {
const [state, dispatch] = useReducer(reducer, 0, init);
return (
<BankContext.Provider value={{
state,
dispatch // 把 dispatch 也作为 context 的一部分共享下去,从而在嵌套组件中调用以实现更新顶层的 state
}}>
<Layout>
<Content />
</Layout>
</BankContext.Provider>
);
}
// 子组件
function Layout(props) {
return (
<div style={{ border: '5px solid lightblue', padding: '20px' }}>
<p>您就当我是个 Layout 组件吧!</p>
{props.children}
</div>
);
}
// 孙组件
// 经过层层嵌套后,可以在孙组件中读取全局 state 并设置
function Content() {
// 这里不要误会,useContext(BankContext) 返回值就是我们共享出来的 context,
// 只是这里刻意把 context 设计为对象,以便同时提供 dispatch
const { state, dispatch } = useContext(BankContext);
return (
<div style={{ border: '1px solid #666' }}>
<div> 当前余额:{state.balance}</div>
<button onClick={() => dispatch({ type: 'deposit', payload: 100 })}>存入100元</button>
</div>
);
}