likes
comments
collection
share

React源码系列(八)------ Context

作者站长头像
站长
· 阅读数 53

前言

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;

React源码系列(八)------ Context

看到以上效果,大家有没感觉非常熟悉?

如果我们将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>
  );
};

React源码系列(八)------ Context

是的,会报错,无法从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的例子的构建过程。

React源码系列(八)------ Context

结尾

以上就是本文的所有内容了,通过上述内容我们理解了context的工作原理,理解了为何使用context时一定要使用Provider去包裹,也理解了尽管共同使用同一个context,只要不被同一个Provider包裹着,数据也不会串台。

总的来说context属于比较容易的部分,自己手写一个的话也比较简单,但是在源码中,还有很多关于context细节的小宝藏(不了解这些宝藏对使用context也没什么影响,纯拓展),这部分就靠大家去源码中调试获取了,本文就不再赘述了。

因为context比较容易,本文没有仅包含至context内容的代码,这次仅附上源码。

完整的源码:点这里

上一篇:useEffect

转载自:https://juejin.cn/post/7247888913866735677
评论
请登录