React Context真的像想象中那么好吗?在使用过程中我们会遇到怎样的坑呢?
现在我就假设用Context代替redux,下面给出一个点击按钮实现数值叠加的小例子:
import React from 'react';
const TestContext = React.createContext();
export default TestContext;
因为用到React.createContext()的地方比较多,所以这里给提取出来单独处理。
function App() {
const [count, setCount] =useState(0);
return (
<TestContext.Provider value={{ count, setCount }}>
<Parent>
<Borther />
<Child />
</Parent>
</TestContext.Provider>
);
}
export default App;
上面例子中的App组件是应用的最顶层组件,渲染出一个Context的Provider,Provider的子组件是应用中其他组件。
App组件内通过useState定义一个state,count和改变state的方法,setCount,并将count和setCount传递给Provider的value属性,在Provider内共享。Provide内的组件可以通过useContext,来获取这两个值。例如在Parent组件内通过useContext来获取setCount,并在button的点击事件中,通过调用setCount来改变count值:
const Parent = ({ children }) => {
console.log('Parent Component Render');
const { setCount } =useContext(TestContext);
return (
<div>
<p>这里是父组件</p>
{children}
<button
onClick={() => {
setCount((preCount) =>++preCount);
}}
>
点我+1
</button>
</div>
);
};
export default Parent;
子组件代码如下:
const Child = () => {
const { count } =useContext(TestContext);
console.log('count: ', count);
return <div>这里是子组件,count: {count}</div>;
};
export default Child;
兄弟组件代码如下:
import React from 'react';
const Borther = () => {
console.log('Borther Component Render');
return <div>这里有一个兄弟组件</div>;
};
export default Borther;
此时,一个完整的界面就出来了,如下,点击button可实现数值的累加:
此时功能一切正常,点击按钮确实可以实现累加,但是你有没有发现,当Context.Provider重新渲染的时候,它所有的子组件都被重新渲染了,比如上面例子中子组件有Borther和Child,Child为useContext之一重画没问题,但是Borther不是useContext,也不依赖于Context的value,根本没有必要重画啊!
但是实际打印出来结果如图:
这是为什么呢?其实,Context.Provider说到底还是组件,也按照组件基本法来办事,当value发生变化时,它也可以不引发子组件的渲染,前提是,子组件作为一个属性(children)也要保持不变才行,如果子组件变了,Context.Provider 也不知道你是不是以前的你,只好让你重画了。我们上面的例子jsx转译成React.createElement运行时类似这样:
React.createElement(Context.Provider, {value: ...}, React.createElement('div', {className: ...}, React.createElement(Header), React.createElement(Content), ) )
每次渲染都会调用React.createElement(),所以导致每次渲染的子组件都是不一样的,怎么样才能让context.provider知道子组件是没有变化的呢,方法也比较简单,就是建一个独立的组件来管理state和Provider,把子组件的JSX写在这个组件之外。把App改成一个无状态组件,只渲染一次,因为state改为ContextWrapper来管理,每次当ContextWrapper的state被setCount改变而重新渲染的时候,它看到的子组件(children)是App传给他的,不需要重新用React.createElement创建,所以children是不变的,于是Context.Provider也就不会让children重新渲染了,代码如下:
const ContextWrapper = ({ children }) => {
const [count, setCount] =useState(0); return (
<TestContext.Provider value={{ count, setCount }}>
{children}
</TestContext.Provider>
);
};
function App() {
return (
<ContextWrapper>
<Parent>
<Borther />
<Child />
</Parent>
</ContextWrapper>
);
}
export default App;
改进后代码打印如下:
此时就不会重复渲染brother组件了。
好了,以上就是一个完整的context避坑小指南,context虽好,但使用需谨慎!!!!