写点什么

react 源码解析 17.context

作者:zz1998
  • 2021 年 12 月 07 日
  • 本文字数:4298 字

    阅读完需:约 14 分钟

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.总结&第一章的面试题解答

查看视频调试 demo_7

context 流程图


cursor/valueStack

react 源码中存在一个 valueStack 和 valueCursor 用来记录 context 的历史信息和当前 context,另外还有一个 didPerformWorkStackCursor 用来表示当前的 context 有没有变化


//ReactFiberNewContext.new.jsconst valueCursor: StackCursor<mixed> = createCursor(null);
复制代码


const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
复制代码


//ReactFiberStack.new.jsconst 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;}
复制代码


用户头像

zz1998

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
react源码解析17.context