【React源码解读】Context && ConcurrentMode

context

在线源码地址:https://github.com/facebook/react/blob/master/packages/react/src/ReactContext.js

两种实现方式

  • childContextType (17版本将被废弃)
  • createContext (16版本更新)

为什么要弃用旧的api?

老的api对context的提供方下层所有组件影响太大了,它会导致它下层所有的组件(即便该组件在没有更新的情况下),它每次更新的情况下,都会重新渲染

demo

import React from 'react'
import PropTypes from 'prop-types'

const { Provider, Consumer } = React.createContext('default')

class Parent extends React.Component {
  state = {
    childContext: '123',
    newContext: '456',
  }

  getChildContext() {
    return { value: this.state.childContext, a: 'aaaaa' }
  }

  render() {
    return (
      <>
        <div>
          <label>childContext:</label>
          <input
            type="text"
            value={this.state.childContext}
            onChange={e => this.setState({ childContext: e.target.value })}
          />
        </div>
        <div>
          <label>newContext:</label>
          <input
            type="text"
            value={this.state.newContext}
            onChange={e => this.setState({ newContext: e.target.value })}
          />
        </div>
        <Provider value={this.state.newContext}>{this.props.children}</Provider>
      </>
    )
  }
}

class Parent2 extends React.Component {
  // { value: this.state.childContext, a: 'bbbbb' }
  getChildContext() {
    return { a: 'bbbbb' }
  }

  render() {
    return this.props.children
  }
}

function Child1(props, context) {
  console.log(context)
  return <Consumer>{value => <p>newContext: {value}</p>}</Consumer>
}

Child1.contextTypes = {
  value: PropTypes.string,
}

class Child2 extends React.Component {
  render() {
    return (
      <p>
        childContext: {this.context.value} {this.context.a}
      </p>
    )
  }
}

// Child2.contextType = Consumer

Child2.contextTypes = { // 通过这种方式,告诉react在渲染过程中,Child2组件希望去获取它的父层组件中所有传递的context中的某几个
  value: PropTypes.string,
  a: PropTypes.string,
}

Parent.childContextTypes = { // 声明传递给子组件的context
  value: PropTypes.string,
  a: PropTypes.string,
}

Parent2.childContextTypes = { 
  a: PropTypes.string,
}

export default () => (
  <Parent>
    <Parent2>
      <Child1 />
      <Child2 />
    </Parent2>
  </Parent>
)

源码

export function createContext<T>(
  defaultValue: T, 
  calculateChangedBits: ?(a: T, b: T) => number,
) {
    /**
    calculateChangedBits: 一个方法,用来计算新老context变化
    
    */

    const context: ReactContext<T> = {
        $$typeof: REACT_CONTEXT_TYPE,
        _calculateChangedBits: calculateChangedBits,
        
        _currentValue: defaultValue,
        _currentValue2: defaultValue,
       
        Provider: (null: any),
        Consumer: (null: any),
     };
     
     /**
     $$typeof: 与ReactElement的$$typeof不一样
     _currentValue和_currentValue2用处是一样的,只是用的地方不一样,比如不同的平台不一样
     _currentValue: 用来记录Prvoider上面提供的value有变化的情况下,就会更新到这个_currentValue上面,就是用来记录最新的context的值的
     
     */
}


ConcurrentMode

16版本以后提出的功能,其目标让react整体渲染过程有一个优先级排比,并整体的渲染过程能够中断,他就可以进行一个任务的调度,更好的利用cpu性能。react能够让我们去区分一些优先级高低的任务,在进行一个react更新的过程中,优先执行一些较高的任务。

  <ConcurrentMode>
    <Parent />
  </ConcurrentMode>

ConcurrentMode有一个特性,在一个子树当中渲染了ConcurrentMode之后,它下面的所有节点产生的更新 都是一个低优先级的更新

demo

import React, { ConcurrentMode } from 'react'
import { flushSync } from 'react-dom' // 能够强制某一个更新操作的时候,使用一个优先级最高的更新

import './index.css'

class Parent extends React.Component {
  state = {
    async: true,
    num: 1,
    length: 2000,
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      this.updateNum()
    }, 200)
  }

  componentWillUnmount() {
    // 别忘了清除interval
    if (this.interval) {
      clearInterval(this.interval)
    }
  }

  updateNum() {
    const newNum = this.state.num === 3 ? 0 : this.state.num + 1
    if (this.state.async) {
      this.setState({
        num: newNum,
      })
    } else {
      flushSync(() => {
        this.setState({
          num: newNum,
        })
      })
    }
  }

  render() {
    const children = []

    const { length, num, async } = this.state

    for (let i = 0; i < length; i++) {
      children.push(
        <div className="item" key={i}>
          {num}
        </div>
      )
    }

    return (
      <div className="main">
        async:{' '}
        <input
          type="checkbox"
          checked={async}
          onChange={() => flushSync(() => this.setState({ async: !async }))}
        />
        <div className="wrapper">{children}</div>
      </div>
    )
  }
}


export default () => (
  <ConcurrentMode>
    <Parent />
  </ConcurrentMode>
)

源码

// React.js
import {
  REACT_CONCURRENT_MODE_TYPE,
  REACT_FRAGMENT_TYPE,
  REACT_PROFILER_TYPE,
  REACT_STRICT_MODE_TYPE,
  REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';
...

if (enableStableConcurrentModeAPIs) {
  React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
  React.Profiler = REACT_PROFILER_TYPE;
} else {
  React.unstable_ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
  React.unstable_Profiler = REACT_PROFILER_TYPE;
}


...

/** 
发现ConcurrentMode竟然是一个常量,于是我们去shared/ReactSymbols下一睹
*/

// ReactSymbols.js
const hasSymbol = typeof Symbol === 'function' && Symbol.for;

...

export const REACT_CONCURRENT_MODE_TYPE = hasSymbol
  ? Symbol.for('react.concurrent_mode')
  : 0xeacf;
  
...
/**
我们发现,ConcurrentMode组件就是一个Symbol,它也没有任何的属性

留有疑问,它到底是如何承载chilren的?后续慢慢深入学习
*/