关于react.js:react源码解析第17节context源码

34次阅读

共计 6639 个字符,预计需要花费 17 分钟才能阅读完成。

react 源码解析第 17 节.context 源码

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

21.demo

查看视频调试 demo_7

context 流程图

cursor/valueStack

react 源码中存在一个 valueStack 和 valueCursor 用来记录 context 的历史信息和以后 context,另外还有一个 didPerformWorkStackCursor 用来示意以后的 context 有没有变动

//ReactFiberNewContext.new.js
const valueCursor: StackCursor<mixed> = createCursor(null);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
//ReactFiberStack.new.js
const valueStack: Array<any> = [];
function pushProvider(providerFiber, nextValue) {
  var context = providerFiber.type._context;
  {push(valueCursor, context._currentValue, providerFiber);
    context._currentValue = nextValue;
  }
}
function popProvider(providerFiber) {
  var currentValue = valueCursor.current;
  pop(valueCursor, providerFiber);
  var context = providerFiber.type._context;

  {context._currentValue = currentValue;}
}
  • 在 render 阶段调用 updateContextProvider 的时候会执行 pushProvider,将新的值 push 进 valueStack 中
  • 在 commit 阶段调用 completeWork 的时候会执行 popProvider,将栈顶 context pop 进去,

为什么会有这样一个机制呢,因为咱们的 context 是跨层级的,在之前讲到 render 阶段和 commit 阶段的时候,咱们会以深度优先遍历的形式遍历节点,如果波及跨层级读取状态就有点力不从心了,就须要一层一层往下传递咱们的 props,所以咱们能够用一个 stack 记录咱们的 context,在 render 阶段 pushProvider,在 commit 阶段 popProvider,在每个具体的层级能依据 valueCursor 取以后 value

createContext

export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {if (calculateChangedBits === undefined) {// 能够传入计算 bit 的函数
    calculateChangedBits = null;
  } else {//...}

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,// 计算 value 变动的函数
    _currentValue: defaultValue,//dom 环境的 value
    _currentValue2: defaultValue,//art 环境的 value
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
  };

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



  if (__DEV__) { } else {context.Consumer = context;}


  return context;
}

// 示例
const NameChangedBits = 0b01;
const AgeChangedBits =  0b10;
const AppContext = createContext({}, (prevValue, nextValue) => {
  let result = 0;
  if (prevValue.name !== nextValue.name) {result |= NameChangedBits;};
  if (prevValue.age !== nextValue.age) {result |= AgeChangedBits;};
  return result;
});

在简化之后的 createContext 中能够看到,context 和 Provider、Consumer 的关系是这样的:

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

useContext

useContext 会调用 readContext,readContext 会创立 dependce,退出以后 fiber 的 dependencies 链表中

function readContext(context, observedBits) {
  {if (lastContextWithAllBitsObserved === context) ; else if (observedBits === false || observedBits === 0) ; else {
    var resolvedObservedBits;

    // 生成 resolvedObservedBits
    if (typeof observedBits !== 'number' || observedBits === MAX_SIGNED_31_BIT_INT) {
      lastContextWithAllBitsObserved = context;
      resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
    } else {resolvedObservedBits = observedBits;}

    var contextItem = {// 生成 dependce
      context: context,
      observedBits: resolvedObservedBits,
      next: null
    };

    if (lastContextDependency === null) {
      //...

      lastContextDependency = contextItem;
      currentlyRenderingFiber.dependencies = {//dependencies 链表的第一个
        lanes: NoLanes,
        firstContext: contextItem,
        responders: null
      };
    } else {lastContextDependency = lastContextDependency.next = contextItem;// 退出 dependencies 链表}
  }

  return  context._currentValue ;
}

provider/customer

在 render 阶段会调用 updateContextProvider,留神几个要害的步骤

  • pushProvider:将以后 context 退出 valueStack
  • calculateChangedBits:useContext 能够设置 observedBits,没有设置的话就是 MAX_SIGNED_31_BIT_INT,也就是 31 位 1,用于计算 changedBits,这个计算 context 是否变动的过程就产生在 calculateChangedBits 函数中,用这样的形式能够进步 context 变动之后的性能
  • bailoutOnAlreadyFinishedWork/propagateContextChange:如果 changedBits 没有扭转则走 bailoutOnAlreadyFinishedWork 的逻辑,跳过以后节点的更新,如果扭转则执行 propagateContextChange
function updateContextProvider(current, workInProgress, renderLanes) {
  var providerType = workInProgress.type;
  var context = providerType._context;
  var newProps = workInProgress.pendingProps;
  var oldProps = workInProgress.memoizedProps;
  var newValue = newProps.value;

  //...

  pushProvider(workInProgress, newValue);

  if (oldProps !== null) {
    var oldValue = oldProps.value;
    var changedBits = calculateChangedBits(context, newValue, oldValue);

    if (changedBits === 0) {//context 没有扭转
      if (oldProps.children === newProps.children && !hasContextChanged()) {return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }
    } else {//context 扭转了
      propagateContextChange(workInProgress, context, changedBits, renderLanes);
    }
  }

  var newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}
function calculateChangedBits(context, newValue, oldValue) {if (objectIs(oldValue, newValue)) {
        // 没有扭转
    return 0;
  } else {var changedBits = typeof context._calculateChangedBits === 'function' ? context._calculateChangedBits(oldValue, newValue) : MAX_SIGNED_31_BIT_INT;

    {if ((changedBits & MAX_SIGNED_31_BIT_INT) !== changedBits) {error('calculateChangedBits: Expected the return value to be a' + '31-bit integer. Instead received: %s', changedBits);
      }
    }

    return changedBits | 0;
  }
}

// 示例
const NameChangedBits = 0b01;
const AgeChangedBits = 0b10;
const AppContext = createContext({}, (prevValue, nextValue) => {
  let result = 0;
  if (prevValue.name !== nextValue.name) {result |= NameChangedBits;};
  if (prevValue.age !== nextValue.age) {result |= AgeChangedBits;};
  return result;
});
function propagateContextChange(workInProgress, context, changedBits, renderLanes) {
  var fiber = workInProgress.child;

  if (fiber !== null) {fiber.return = workInProgress;//fiber 不存在 就找父节点}

  while (fiber !== null) {
    var nextFiber = void 0;// 遍历 fiber

    var list = fiber.dependencies;

    if (list !== null) {
      nextFiber = fiber.child;
      var dependency = list.firstContext;

      while (dependency !== null) {// 遍历 dependencies 链表
        if (dependency.context === context && (dependency.observedBits & changedBits) !== 0) {
                    // 有变动
          if (fiber.tag === ClassComponent) {
            // 创立新的 update
            var update = createUpdate(NoTimestamp, pickArbitraryLane(renderLanes));
            update.tag = ForceUpdate; 
            enqueueUpdate(fiber, update);
          }

          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);// 合并优先级
          var alternate = fiber.alternate;

          if (alternate !== null) {alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
          }

          scheduleWorkOnParentPath(fiber.return, renderLanes); // 更新先人节点的优先级

          list.lanes = mergeLanes(list.lanes, renderLanes); 
          break;
        }

        dependency = dependency.next;
      }
    } 
        //...
      nextFiber = fiber.sibling;
    } else {nextFiber = fiber.child;}
    //...

    fiber = nextFiber;
  }
}

updateContextConsumer 要害的代码如下,执行 prepareToReadContext 判断优先级是否足够退出以后这次 render,readContext 取到以后 context 的 value

function updateContextConsumer(current, workInProgress, renderLanes) {
  var context = workInProgress.type;
  //...
  prepareToReadContext(workInProgress, renderLanes);
  var newValue = readContext(context, newProps.unstable_observedBits);
  var newChildren;
  {
    ReactCurrentOwner$1.current = workInProgress;
    setIsRendering(true);
    newChildren = render(newValue);
    setIsRendering(false);
  }

    //...
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}

正文完
 0