有人说:构建
React
应用就像用积木搭房子,每个组件
就是一块积木。
这么说的人可能忽视了state
(状态)—— 不同于组件
是以树的形式组合,我们经常会在不同层级的组件间公用同一个state
。
面对这种情况,有些同学选择引入状态管理库
(比如Redux
)。
事实上,React
内置的context
API可以解决大部分状态传递问题。
本文接下来要讲的,就是如何更有效的使用context
。
在我们的Demo中,有个context
——CountContext
。
当其render
时如果上层结构中不存在context provider
为他提供context value
,则在解构context value
时会报错。
const CountContext = React.createContext();
function CountDisplay() {
// 解构语法报错
const {count} = React.useContext(CountContext);
return <div>{count}</div>;
}
这是因为CountContext
没有默认值
,所以为undefined
。将undefined
当作对象解构时报错。
在有些场景下默认值
是有意义的。但是大多数情况,context consumer
需要context provider
为他提供有用的context value
。
这意味着使用context
的业务组件需要判断undefined
,否则可能出现运行时错误。
为了解决这个问题,我们可以用自定义hook
将错误前置
。
// src/count-context.js
const CountContext = React.createContext();
function useCount() {
const context = React.useContext(CountContext);
if (context === undefined) {
throw new Error('必须在CountProvider内使用useCount');
}
return context;
}
同时提供CountProvider
:
// src/count-context.js
function CountProvider({children}) {
const stateHook = React.useState();
return (
<CountContext.Provider value={stateHook}>
{children}
</CountStateContext.Provider>
)
}
在需要CountContext
的业务组件中,我们不再需要直接使用CountContext
,而是引入useCount
与CountProvider
:
// src/app.jsx
import {useCount, CountProvider} from './count-context.js'
function CountDisplay() {
const [count] = useCount();
return <div>{count}</div>;
}
function App() {
return (
<CountProvider>
<CountDisplay />
</CountProvider>
)
}
如果在未包裹CountProvider
的情况下单独使用useCount
,会直接抛出错误,而不需要等到使用context
时再报错。
这种将错误前置的方式能够帮我们更好的规避运行时错误
。
在CountProvider
中,stateHook
作为context value
传递给context consuer
。
function CountProvider({children}) {
const stateHook = React.useState();
// ...
}
其中state
与改变state
的方法(dispatch)同时存在于context value
中。
有些时候,展示state
的组件与触发state
变化的组件不是同一个组件,比如:
function App() {
return (
<CountProvider>
<CountDisplay />
<Counter />
</CountProvider>
)
}
其中CountDisplay
用于展示state
。
Counter
用于触发state
变化。
由于state
与dispatch
同时存在于context value
,state
变化后CountDisplay
与Counter
都会重新render
。
这对于只负责触发组件state
变化的Counter
来说是不必要的。
为此,我们可以将state
与dispatch
分离。
修改CountProvider
:
// src/count-context.js
const CountStateContext = React.createContext();
const CountDispatchContext = React.createContext();
function CountProvider({children}) {
const [state, dispatch] = React.useState();
return (
<CountStateContext.Provider value={state}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
同时,useCount
也拆分为useCountState
与useCountDispatch
:
// src/count-context.js
function useCountState() {
const context = React.useContext(CountStateContext);
if (context === undefined) {
throw new Error('必须在CountProvider内使用useCountState');
}
return context;
}
function useCountDispatch() {
const context = React.useContext(CountDispatchContext);
if (context === undefined) {
throw new Error('必须在CountProvider内使用useCountDispatch');
}
return context;
}
在CountDisplay
中使用useCountState
。
在Counter
中使用useCountDispatch
。
这样,即使state
变化,只有CountDisplay
会render
,Counter
不会render
。
事实上,将context
分离为动态(state),静态(dispatch)两部分后可以拥有更灵活的拓展。
当需要更多state
时,可以将CountProvider
的useState
替换为useReducer
:
// src/count-context.js
function CountProvider({children}) {
const [state, dispatch] = React.useReducer(countReducer);
return (
<CountStateContext.Provider value={state}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
当需要更多方法
时,你也可以拓展CountDispatchContext
。
通过这套context
的“更”佳实践,审视下我们现有的业务,是不是很多时候并不需要额外的状态管理库
呢?
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有