React源码系列(八)------ Context
前言
context作为夸组件的传参方式,大家是再熟悉不过了,它解决了我们props下钻的问题,使我们不需要一层一层传递props,今天我们就来看看他的具体原理吧。
context
context原理
在讲context原理前,我们先来看个简单例子来帮我们理解context。
随意启动一个react项目,然后写下以下两个文件,然后启动项目。
// data.js
export const data = {
inner: {},
};
export const setData = (key, value) => {
data.inner[key] = value;
};
export const clearData = () => {
data.inner = {};
};
// App.jsx
import { data, setData, clearData } from './data';
const App = () => {
const change = () => {
setData('name', '猪头切图仔')
};
const printData = () => {
console.log(data);
}
return (
<div>
<button onClick={printData}>打印data数据</button>
<button onClick={change}>改变data数据</button>
<button onClick={clearData}>清除data数据</button>
</div>
);
};
export default App;
看到以上效果,大家有没感觉非常熟悉?
如果我们将createContext类比成生成一个data对象。
// 这个Context可以类比成data这个对象
const Context = React.createContext();
将在Provider的value这个Props类比成data中的inner。
import Context from './context';
const App = () => (
<Context.Provider value={{ name: '猪头切图仔' }}>
<Child />
<Context.Provider>
);
将useContext类比成引入data。
const Child = () => {
const context = React.useContext(Context);
};
现在是不是感觉自己抓到了context的原理了?而实际上,context的原理也就只是这样子,所谓的context就是一个全局的变量,然后再加一点点细节,那就是在适当的时候写入数据,然后再适当时候清空数据。
context数据的写入与清空
在抛出答案之前,我们来看个demo,然后思考一个问题。
问题:请问child1和child2的打印结果是什么?为什么会出现这种情况?(代码后方就是结果,若不想被干扰,请谨慎滑动)
const Context = React.createContext();
const App = () => (
<div>
<Context.Provider value={{ name: '猪头切图仔' }}>
<Child key="child1" />
</Context.Provider>
<Child key="child2" />
</div>
);
const Child = () => {
const appContext = React.useContext(Context);
return (
<button onClick={() => console.log(appContext.name)}>打印name</button>
);
};
是的,会报错,无法从undefined中读取name,正如上面原理部分所说的,将Provider的value类比于inner,而Child2没被Provider包裹着,当读data.inner.name时,由于此时data是一个空对象,无inner属性,所以就会报这个错。
但是,为什么呢?如果context只是一个全局变量,只要使用了这个context(引入了data),那么应该都能获取到name才对啊?
上述疑问就是这一小节的主要内容了,而正确的答案就是,Child2在读取context的数据时,context已经被清空了。
我们来看一段源码(简化后)。
function popProvider() {
......
context.currentValue = null;
};
function CompleteWork() {
if (workInProgress.tag === ContextProvider) {
popProvider();
......
}
};
上述源码很清晰的告诉我们,context存的东西,会在completeWork时清空,这时我们就明白了,Provider在自己的beginWork阶段将value值写入到context这个全局变量中,然后Provider在自己的completeWork阶段将context清空,而又因为前面几章节所讲的,fiber是深度优先,所以会优先构造Provider的Children的fiber,所以所有被Provider包裹的Children都能读到value。
上面这段文字有点长,理解起来或许会有点费劲,那我们来还原一下child1和child2那个demo的例子的构建过程。
结尾
以上就是本文的所有内容了,通过上述内容我们理解了context的工作原理,理解了为何使用context时一定要使用Provider去包裹,也理解了尽管共同使用同一个context,只要不被同一个Provider包裹着,数据也不会串台。
总的来说context属于比较容易的部分,自己手写一个的话也比较简单,但是在源码中,还有很多关于context细节的小宝藏(不了解这些宝藏对使用context也没什么影响,纯拓展),这部分就靠大家去源码中调试获取了,本文就不再赘述了。
因为context比较容易,本文没有仅包含至context内容的代码,这次仅附上源码。
完整的源码:点这里
上一篇:useEffect
转载自:https://juejin.cn/post/7247888913866735677