手写React useContext,理解useContext原理

86 阅读1分钟

一. useContext方法介绍

useContext方法接收一个参数Context,返回Context记录的属性值

例如下面这段代码,创建一个CounterContext,将Appcount状态值赋值给CounterContext,子组件通过useContext方法获取CounterContext记录的属性值

const CounterContext = createContext()

function HelloWorld() {
  const value = useContext(CounterContext)

  return <h1>{value}</h1>
}

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <h1 onClick={() => setCount(count + 1)}>click</h1>
      <CounterContext.Provider value={count}>
        <HelloWorld />
      </CounterContext.Provider>
    </div>
  )
}

二. 实现createContext

强烈推荐阅读手写mini React,理解React渲染原理,有助于理解本文章内容

2.1 定义createContext方法

创建elementType对象,$$typeof属性记录ReactElement对象类型,_currentValue属性记录传递给子组件的值

export function createContext(defaultValue) {
  const context = {
    $$typeof: REACT_CONTEXT_TYPE, // Symbol.for('react.context')
    _currentValue: defaultValue,
  }
  context.Provider = context

  return context
}

以这段示例代码为例<CounterContext.Provider value={0}><HelloWorld /></CounterContext.Provider>,创建的ReactElement对象结构如下

{
  $$typeof: Symbol.for('react.transitional.element'),
  key: null,
  type: {
    $$typeof: Symbol.for('react.context'),
    _currentValue: null,
    Provider: self,
  },
  props: {
    value: 0,
    children: {
      $$typeof: Symbol.for('react.transitional.element'),
      key: null,
      type: function HelloWorld() {},
      props: {},
    },
  },
}

2.2 创建ReactElement对象对应的FiberNode节点

根据ReactElement对象的type属性创建对应的FiberNode节点,FiberNode节点的tag属性值为ContextProvider

function createFiberFromElement(element) {
  let fiberTag
  const { type } = element
  if (typeof type === 'function') {
    fiberTag = FunctionComponent
  } else if (typeof type === 'string') {
    fiberTag = HostComponent
  } else {
    switch (type.$$typeof) {
      // ContextProvider类型FiberNode节点
      case REACT_CONTEXT_TYPE:
        fiberTag = ContextProvider
        break
    }
  }
  const fiber = new FiberNode(fiberTag, element.props)
  fiber.key = element.key
  fiber.elementType = type
  coerceRef(fiber, element)
  return fiber
}

2.3 更新ContextProvider._currentValue

在构建虚拟DOM树阶段,递归遍历到ContextProvider类型的FiberNode节点时,会将props属性的value赋值给ContextProvider._currentValue

function updateContextProvider(current, workInProgress) {
  // 获取ContextProvider
  const context = workInProgress.elementType
  const newProps = workInProgress.pendingProps
  const newValue = newProps.value
  // 将传递给子组件的值赋值给ContextProvider._currentValue
  context._currentValue = newValue
  const newChildren = newProps.children
  reconcileChildren(current, workInProgress, newChildren)
  return workInProgress.child
}

2.4 定义useContext方法

返回ContextProvider._currentValue

function useContext(context) {
  const value = context._currentValue
  return value
}

三. 往期文章推荐

3.1 React原理系列总结

四. 参考文档

4.1 React useContext官方文档