上篇文章《如何使用Hooks和Context API取代Redux》中提到了使用hooks代替redux创建可共享的全局状态的例子,本文参考了一些工程案例的写法并结合个人的实际使用,对上篇文章的hooks的使用做了一些改良,希望能给大家提供不同的思路。
回顾
我们先来回顾一下上篇文章中的状态共享的大致写法:
创建一个
contact-context.js
// 定义默认值
const initialState = {
// 具体省略...
}
// 定义reducer
const reducer = (state, action) => {
// 具体省略...
}
// 返回一个provider
export const ContactContextProvider = props => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ContactContext.Provider value={[state, dispatch]}>
{props.children}
</ContactContext.Provider>
);
};
容器组件引入Provider
export default function Contacts() {
return (<ContactContextProvider>
...
</ContactContextProvider>)
}
具体组件使用useContext引入状态
import { ContactContext } from "../context/contact-context"; export default function ContactTable() { // 获取状态 `state` 和改变状态的函数:dispatch const [state, dispatch] = useContext(ContactContext); ... // state.xxx 获取具体状态的值 // dispatch(action)改变状态的值 }
以上其实会遇到一些实际场景:
1.某些组件里其实并不需要获取state的值,只会用到用于改变状态的dispatch函数(比如一些交互按钮的组件封装),而由于导出顺序和useContext写法的原因,我们只想用dispatch也必需要引入state。并且state改变之后组件也会重新渲染,就可能会引发不必要的重新计算。
2.所有状态都需要通过state.xxx的方式获取,维护者必须需要通过查看contact-context才知道具体都定义了哪些state
3.通常我们实际项目中会定义多组context,当一个组件中依赖了多组context,多个useContext写起来没有那么优雅。改进写法
创建一个context.js
// 定义默认值 const initialState = { state1: '', state2: {} // ... } // 定义reducer const reducer = (state, action) => { // 具体省略... } // 创建provider function XxxProvider({ children }) { var [state, dispatch] = React.useReducer(reducer, initialState) // 将state和dispatch拆分成两个Context return ( <XxxStateContext.Provider value={state}> <XxxDispatchContext.Provider value={dispatch}> {children} </XxxDispatchContext.Provider> </XxxStateContext.Provider> ) } // 提供state的hook useXxxState function useXxxState() { var context = React.useContext(XxxStateContext) if (context === undefined) { throw new Error( 'useXxxState must be used within a XxxProvider' ) } return context } // 提供dispatch的context function useXxxDispatch() { var context = React.useContext(ModuleTreeDispatchContext) if (context === undefined) { throw new Error( 'useXxxDispatch must be used within a XxxProvider' ) } return context }
容器组件引入Provider
这和原来的写法一样,不再重复描述
- 具体组件使用
只依赖state的组件
import { useXxxState } from 'path/to/context'
function (props) {
const {state1, state2} = useXxxState()
// ...
}
只依赖dispatch的组件
import { useXxxDispatch } from 'path/to/context'
function (props) {
const dispatch = useXxxDispatch()
// ... state改变不会引发组件的重新render
}
总结
今天介绍的这种改进写法,主要是提供了useXxxState和useXxxDispatch自定义hooks的封装,和原来的写法相比,代码更佳简洁,命名更佳明确;针对只依赖dispatch不依赖state的组件也会减少不必要的重新render;如果遇到一些需要依赖其他属性计算的state,也可以很好的在useXxxState中统一封装(不过原来的写法也可以在useReducer中进行封装)。具体大家觉得哪种写法更好,可以进一步实践验证一下。