React Context(上下文) 作用和使用

1,690 阅读1分钟

使用场景:当需要给一个嵌套层级较深的一个组件传值或者你就不想使用props传值的时候你可以使用react.createContext

react.createContext的使用方法

1、使用Provider, Consumer
const {Provider, Consumer} = React.createContext(defaultValue);
const ProviderCom =  <Provider value={form}>
    <Child {...props} />
  </Provider> 
 const Child = <Consumer>
    {form => (
      ... //这里可以接收到ProviderCom传递的form参数
    )}
  </Consumer>
2、使用Provider和useContext
const LogStateContext = React.createContext(defaultValue);
const [logs, setLogs] = useState([]);
const ProviderCom = <LogStateContext.Provider value={logs}>
     {children}
  </LogStateContext.Provider>
const LogsPanel = () => {
  const logs = useContext(LogStateContext); //使用useContext可以解析出LogStateContext.Provider传递的logs
  return logs.map((log, index) => <p key={index}>{log}</p>);
}

react.createContext源码分析

function createContext(defaultValue, calculateChangedBits) {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    {
      !(calculateChangedBits === null || typeof calculateChangedBits === 'function') ? warningWithoutStack$1(false, 'createContext: Expected the optional second argument to be a ' + 'function. Instead received: %s', calculateChangedBits) : void 0;
    }
  }

  var context = {
     $$typeof: REACT_CONTEXT_TYPE, //context的$$typeof在createElement中的type中的type对象中存储
     _calculateChangedBits: calculateChangedBits,//计算新老context变化
     //_currentValue和_currentValue2作用一样,只是作用平台不同
     _currentValue: defaultValue, //Provider的value属性
     _currentValue2: defaultValue,
     _threadCount: 0, //用来追踪context的并发渲染器数量
     // These are circular
     Provider: null, //提供组件
     Consumer: null  //应用组件
  };
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context
  };
  var hasWarnedAboutUsingNestedContextConsumers = false;
  var hasWarnedAboutUsingConsumerProvider = false;

  {
    // A separate object, but proxies back to the original context object for
    // backwards compatibility. It has a different $$typeof, so we can properly
    // warn for the incorrect usage of Context as a Consumer.
    var Consumer = {
      $$typeof: REACT_CONTEXT_TYPE,
      _context: context,
      _calculateChangedBits: context._calculateChangedBits
    }; // $FlowFixMe: Flow complains about not setting a value, which is intentional here

    Object.defineProperties(Consumer, {
      Provider: {
        get: function () {
          if (!hasWarnedAboutUsingConsumerProvider) {
            hasWarnedAboutUsingConsumerProvider = true;
            warning$1(false, 'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Provider> instead?');
          }

          return context.Provider;
        },
        set: function (_Provider) {
          context.Provider = _Provider;
        }
      },
      _currentValue: {
        get: function () {
          return context._currentValue;
        },
        set: function (_currentValue) {
          context._currentValue = _currentValue;
        }
      },
      _currentValue2: {
        get: function () {
          return context._currentValue2;
        },
        set: function (_currentValue2) {
          context._currentValue2 = _currentValue2;
        }
      },
      _threadCount: {
        get: function () {
          return context._threadCount;
        },
        set: function (_threadCount) {
          context._threadCount = _threadCount;
        }
      },
      Consumer: {
        get: function () {
          if (!hasWarnedAboutUsingNestedContextConsumers) {
            hasWarnedAboutUsingNestedContextConsumers = true;
            warning$1(false, 'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Consumer> instead?');
          }

          return context.Consumer;
        }
      }
    }); // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty

    context.Consumer = Consumer;
  }

  {
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}
  • 在context设置Provider和Consumer属性值
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

表示给context的provider设置属性值 如果我们需要设置当前Provider上的value值 则直接使用context.Provider._context._currentValue进行设置

  var Consumer = {
    $$typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };
  context.Consumer = Consumer;

表示给context设置Consumer设置属性值 如果我们需要获取当前通过Provider上的value值 则直接通过自身的Consumer组件的context.Consumer._context._currentValue获取value值

我们看到 createContext ,他接收的是一个 defaultValue ,还有一个是 calculateChangedBits 。是一个方法,是用来计算新老 context 的一个变化的。方法里面看到他声明了一个 context 对象,这个对象跟之前看的 ReactElement 非常的像。也有一个 typeof,这个typeof ,这个 typeof 跟 ReactElement 的 $$typeof 是不一样的。还有两个属性 _currentValue , _currentValue2 这两个属性是一样的,只是用到的地方不一样。 _currentValue 这个 value 是用来记录 Provider 里面的这个 value 。 他有变化的情况下就会更新到这个 _currentValue 。这就是用来记录最新的 context 的值的, 下面会有个 Provider 和 Consumer 。

下面有个 context.Provider ,他有个属性 _context ,这个属性会指向这个 context。context.Consumer = context。 也就是说 Consumer 是等于自己的。

到这我们知道 Consumer 就是指向这个对象的时候,可以猜到,在 Consumer 进行渲染的时候,他要去获取这个 value 怎么办呢,他只要从自己本身上面 _currentValue 就可以拿到最新的 context 的 value 。然后再调用那个方法把他传进去。这就是基本的实现原理,真正的实现没有这么简单。

这里面的 typeof并不是要替换ReactElementtypeof 并不是要替换 ReactElement 的 typeof。而是这里面的 $$typeof 是指 ReactElement 里面的 type.