react源码分析/createContext

Posted web前端每日干货

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码分析/createContext相关的知识,希望对你有一定的参考价值。

createContext.js


先看一个demo

import React from 'react'
import styles from './index.css'
const ThemeContext = React.createContext('light')

console.log(ThemeContext)
export default class Test extends React.Component {
  constructor(props) {
    super(props)
    this.state = { theme: 'light' }
  }
  toggleTheme = () => {
    this.setState(({ theme }) => ({
      theme: theme === 'light' ? 'dark' : 'light'
    }))
  }
  render() {
    return (
      <React.Fragment>
        <ThemeContext.Provider value={this.state.theme}>
          <AppBody onClick={this.toggleTheme} />
          <div />
        </ThemeContext.Provider>
      </React.Fragment>
    )
  }
}

class AppBody extends React.PureComponent {
  render() {
    console.log('AppBody rendered')
    const { onClick } = this.props
    return (
      <ThemeContext.Consumer>
        {theme => (
          <button className={`${theme}`} onClick={onClick}>
            {theme}
          </button>
        )}
      </ThemeContext.Consumer>
    )
  }
}

按钮点击,butoon 的颜色发生变了。但是你获取奇怪。这里使用

react.PureComponent,组件本身会对props,state进行简单钱比较,然后决定要不要更新,即是shouldComponentUpdate 返回 false,不进行更新。但是点击按钮,按钮背景色却发生了变化。主要的原因就是React.createContext()  

分析一下ReactContext.js 


*ReactContext的类型       

// 多renderer 并发工作
export type ReactContext<T> = {
  $$typeofSymbol | number,
  Consumer: ReactContext<T>,
  Provider: ReactProviderType<T>,

  _calculateChangedBits((a: T, b: T) => number) | null,

  _currentValue: T,
  _currentValue2: T,
  _threadCount: number,

  // DEV only 
  _currentRenderer?: Object | null,
  _currentRenderer2?: Object | null,
};

 *ReactProviderType类型

export type ReactProviderType<T> = {
  $$typeof: Symbol | number,
  _context: ReactContext<T>,
};


ReactContext.js代码主要部分为:

xport function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    if (__DEV__) {
      warningWithoutStack(
        calculateChangedBits === null ||
          typeof calculateChangedBits === 'function',
        'createContext: Expected the optional second argument to be a ' +
          'function. Instead received: %s',
        calculateChangedBits,
      );
    }
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
  };
// Consumer和Provider两个属性很有意思,存在循环引用
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;

  if (__DEV__) {
    const Consumer = {
      $$typeof: REACT_CONTEXT_TYPE,
      _context: context,
      _calculateChangedBits: context._calculateChangedBits,
    };
    Object.defineProperties(Consumer, {
      Provider: {
        get() {
          if (!hasWarnedAboutUsingConsumerProvider) {
            hasWarnedAboutUsingConsumerProvider = true;
            warning(
              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(_Provider) {
          context.Provider = _Provider;
        },
      },
      _currentValue: {
        get() {
          return context._currentValue;
        },
        set(_currentValue) {
          context._currentValue = _currentValue;
        },
      },
      _currentValue2: {
        get() {
          return context._currentValue2;
        },
        set(_currentValue2) {
          context._currentValue2 = _currentValue2;
        },
      },
      _threadCount: {
        get() {
          return context._threadCount;
        },
        set(_threadCount) {
          context._threadCount = _threadCount;
        },
      },
      Consumer: {
        get() {
          if (!hasWarnedAboutUsingNestedContextConsumers) {
            hasWarnedAboutUsingNestedContextConsumers = true;
            warning(
              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;
        },
      },
    });
    context.Consumer = Consumer;
  } else {
    context.Consumer = context;
  }

  if (__DEV__) {
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}


1.provider和consumer 其实都是context

context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

 dev环境:

context.Consumer= {Provider: {},  _currentValue: {},  _currentValue2: {}, _threadCount: {},Consumer: {},}

其他环境:

context.Consumer = context;


1.然后你会发现 不管是provider 还是consumer 都是context。

2.

currentValue=defaultValue

3.当使用

<ThemeContext.Provider>

的时候,其实已经拿到整体的context.而使用

<ThemeContext.Consumer>

也拿到了整体的context。

当provider 的值改变的时候,其实consumer 是从当前的context._currentValue 中获取对应的value,静态的方法,自然是不走生命周期的,所以在pureComponent 中状态改变依然能出发按钮的背景颜色的变化。


总结:

 * ReactContext本质就是跨域中间的components的通信。
 * 1.只要维护好value,没有key 创建的时候给定默认值,通过provider 组件写,通过Consumer 组件来读
 * 2.一个provider 可以对应多个consumer,内层provider 能够重写外层provider 值,实际上是读取与之最近的一个provider
 * 3.provider 的value pros 发生变化时会通知所有的后代consumer 重新渲染(直接渲染,不走的shouldComponentUpdate)。
 * 4.Consumer 没有与之匹配的Provider ,就走default
 * 5.Consumer 也可以不需要Provider 自己跑
 * 6.新旧value 变化,走的是 Object.is() 浅比较,因此数据尽量要扁平化


以上是关于react源码分析/createContext的主要内容,如果未能解决你的问题,请参考以下文章

带有 React.createContext 的 Typescript HOC

为啥我不能使用 react.createContext()

如何将 React.createContext() 与反应路由器一起使用?

有啥实用的方法可以在组件中调用“React.createContext()”吗?

相同的 React.createContext 在功能组件和 React.Components 中是如何工作的

使用 null 作为初始值时,`React.createContext(null)` 的打字稿类型