React16源码: React中LegacyContext的源码实现

AArch64 ARM64 寄存器介绍 除了上述通用寄存器,还有FPSR(浮点状态寄存器)和 FPCR(浮点控制寄存器)等。X29寄存器和X30寄存器在函数调用和堆栈帧的管理中扮演着关键角色。AArch64架构提供了31个通用寄存器,每个寄存器都可以用作64位X寄存器(X0~X30)或32位W寄存器(W0~W30)。使用X寄存器将导致64位的计算,使用W寄存器将导致32位的计算。EL0、EL1、EL2和EL3是AArch64架构中定义的四个特权级别(Exception Levels),用于支持不同的执行环境和特权级别。 阅读详情

LegacyContext

  • 老的 contextAPI 也就是我们使用 childContextTypes 这种声明方式
  • 来从父节点为它的子树提供 context 内容的这么一种方式
  • 遗留的contextAPI 在 react 17 被彻底移除了,就无法使用了
  • 那么为什么要彻底移除这个contextAPI的使用方式呢?
  • 因为它对性能的影响会比较的大,它会影响整个子树的一个更新过程
  • 它嵌套的context提供者是需要进行一个数据的合并的
    • 嵌套组件中,如果父子两者都提供相同的变量,会进行一个合并
    • 越接近里层,越会被选择
    • 也就是说在孙子消费变量的时候,选择父亲的,舍弃爷爷的

2 )源码

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L1522

在这里,有这个判断 if ( oldProps === newProps && !hasLegacyContextChanged() && updateExpirationTime < renderExpirationTime ) {}

看到有

import {
  hasContextChanged as hasLegacyContextChanged
} from './ReactFiberContext';

关注 hasLegacyContextChanged 基于此,定位到 packages/react-reconciler/src/ReactFiberContext.js#L115

// 要去推入 stack 的值的时候,就要去创建这么一个 cursor 来标记不同类型的一个值
function hasContextChanged(): boolean {
  return didPerformWorkStackCursor.current;
}

回顾到 context-stack 中

// packages/react-reconciler/src/ReactFiberContext.js#L36

// A cursor to the current merged context object on the stack.
// 用来记录我们当前的我们更新到某一个节点之后,它应该可以拿到的context对应的所有值
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);

// A cursor to a boolean indicating whether the context has changed.
// 代表着我们在更新到某一个节点的时候,它这个context是否有变化这么一个情况
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);

再次回到 ReactFiberBeginWork.js 中的 if ( oldProps === newProps && !hasLegacyContextChanged() && updateExpirationTime < renderExpirationTime ) {}

对于在 beginWork 中的 context 操作,可以看上述判断成立的条件下的代码,即便有符合跳过更新的操作,依然 需要 push 和 pop 操作

进入代码

switch (workInProgress.tag) {
  case ClassComponent: {
    const Component = workInProgress.type;
    // 如果当前组件是一个 provider 则进行 push 操作
    if (isLegacyContextProvider(Component)) {
      pushLegacyContextProvider(workInProgress);
    }
    break;
  }
}
  • 跟遗留的 contextAPI 有关,通过 legency 标志,如果一个组件能够作为一个 context 的提供者

  • 那么它肯定是一个 ClassComponent, 因为要通过 getchildcontext 这么一个方法来声明我们子树当中提供了哪些 concontext

  • 最主要的就是来看在classcomponent的更新过程当中,如果它是一个contextprovider,那么它要执行的操作是 pushLegacyContextProvider

  • 进入 isLegacyContextProvider, 看到它是 isContextProvider 的别名

    // 这个 type 就是组件实例,这个 childContextTypes
    function isContextProvider(type: Function): boolean {
      // 通过判断应用中声明的 class 上面是否有这个属性
      const childContextTypes = type.childContextTypes;
      return childContextTypes !== null && childContextTypes !== undefined;
    }
    
    • 通过声明的这个class给它挂载 childContextTypes 来表示它是一个context的提供者
    • 只有声明了这个之后,它才会作为一个context的provider来提供子树上面的context
    • 如果不这么声明,即便在class里面提供了 getChildContext 这个方法,还是拿不到对应的context
  • 进入 pushLegacyContextProvider 它是 pushContextProvider 的别名

    function pushContextProvider(workInProgress: Fiber): boolean {
      const instance = workInProgress.stateNode;
      // We push the context as early as possible to ensure stack integrity.
      // If the instance does not exist yet, we will push null at first,
      // and replace it on the stack later when invalidating the context.
      const memoizedMergedChildContext =
        (instance && instance.__reactInternalMemoizedMergedChildContext) ||
        emptyContextObject;
    
      // Remember the parent context so we can merge with it later.
      // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
      previousContext = contextStackCursor.current; // 获取之前的 context 挂载到全局变量上
      push(contextStackCursor, memoizedMergedChildContext, workInProgress);
      push(
        didPerformWorkStackCursor,
        didPerformWorkStackCursor.current,
        workInProgress,
      );
    
      return true;
    }
    

以上是可以跳出当前组件的更新的一个处理情况

如果我们可以跳出组件的更新,也就是代表着当前这个 classComponent,它的state它的props都应该是没有任何变化的

这个时候, 当然是可以直接使用保存在它上面原始的 context 的对象

如果它是一个需要更新的 classComponent,需要看一下 updateClassComponent 这个更新方法

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderExpirationTime);

  const instance = workInProgress.stateNode;
  let shouldUpdate;
  if (instance === null) {
    if (current !== null) {
      // An class component without an instance only mounts if it suspended
      // inside a non- concurrent tree, in an inconsistent state. We want to
      // tree it like a new mount, even though an empty version of it already
      // committed. Disconnect the alternate pointers.
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.effectTag |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    constructClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    mountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}
  • 一进来就调用了 isLegacyContextProvider 方法

    • 就是如果它是一个contextprovider,那么它要进行 push 操作 pushLegacyContextProvider
  • 接下去, 它调用了一个方法,叫做 prepareToReadContext 这么一个方法

    • 这个方法和新的contextAPI有关,先跳过
  • 接下去基本上没有跟 context 相关的内容了,这里进入 finishClassComponent

    function finishClassComponent(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: any,
      shouldUpdate: boolean,
      hasContext: boolean,
      renderExpirationTime: ExpirationTime,
    ) {
      // Refs should update even if shouldComponentUpdate returns false
      markRef(current, workInProgress);
    
      const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
    
      if (!shouldUpdate && !didCaptureError) {
        // Context providers should defer to sCU for rendering
        if (hasContext) {
          invalidateContextProvider(workInProgress, Component, false);
        }
    
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
    
      const instance = workInProgress.stateNode;
    
      // Rerender
      ReactCurrentOwner.current = workInProgress;
      let nextChildren;
      if (
        didCaptureError &&
        typeof Component.getDerivedStateFromError !== 'function'
      ) {
        // If we captured an error, but getDerivedStateFrom catch is not defined,
        // unmount all the children. componentDidCatch will schedule an update to
        // re-render a fallback. This is temporary until we migrate everyone to
        // the new API.
        // TODO: Warn in a future release.
        nextChildren = null;
    
        if (enableProfilerTimer) {
          stopProfilerTimerIfRunning(workInProgress);
        }
      } else {
        if (__DEV__) {
          ReactCurrentFiber.setCurrentPhase('render');
          nextChildren = instance.render();
          if (
            debugRenderPhaseSideEffects ||
            (debugRenderPhaseSideEffectsForStrictMode &&
              workInProgress.mode & StrictMode)
          ) {
            instance.render();
          }
          ReactCurrentFiber.setCurrentPhase(null);
        } else {
          nextChildren = instance.render();
        }
      }
    
      // React DevTools reads this flag.
      workInProgress.effectTag |= PerformedWork;
      if (current !== null && didCaptureError) {
        // If we're recovering from an error, reconcile without reusing any of
        // the existing children. Conceptually, the normal children and the children
        // that are shown on error are two different sets, so we shouldn't reuse
        // normal children even if their identities match.
        forceUnmountCurrentAndReconcile(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
      } else {
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
      }
    
      // Memoize state using the values we just used to render.
      // TODO: Restructure so we never read values from the instance.
      workInProgress.memoizedState = instance.state;
    
      // The context might have changed so we need to recalculate it.
      if (hasContext) {
        invalidateContextProvider(workInProgress, Component, true);
      }
    
      return workInProgress.child;
    }
    
    • hasContext 是否是一个 contextProvider
    • 如果是 true, 则执行 invalidateContextProvider(workInProgress, Component, false);
      function invalidateContextProvider(
        workInProgress: Fiber,
        type: any,
        didChange: boolean,
      ): void {
        const instance = workInProgress.stateNode;
        invariant(
          instance,
          'Expected to have an instance by this point. ' +
            'This error is likely caused by a bug in React. Please file an issue.',
        );
      
        // 如果有变化
        if (didChange) {
          // Merge parent and own context.
          // Skip this if we're not updating due to sCU.
          // This avoids unnecessarily recomputing memoized values.
          const mergedContext = processChildContext(
            workInProgress,
            type,
            previousContext,
          );
          instance.__reactInternalMemoizedMergedChildContext = mergedContext;
      
          // Replace the old (or empty) context with the new one.
          // It is important to unwind the context in the reverse order.
          pop(didPerformWorkStackCursor, workInProgress);
          pop(contextStackCursor, workInProgress);
          // Now push the new context and mark that it has changed.
          push(contextStackCursor, mergedContext, workInProgress);
          push(didPerformWorkStackCursor, didChange, workInProgress);
        } else {
          pop(didPerformWorkStackCursor, workInProgress);
          push(didPerformWorkStackCursor, didChange, workInProgress);
        }
      }
      
      • 通过 processChildContext 计算出新的 context 并挂载到 __reactInternalMemoizedMergedChildContext
      • 之后,pop 2次,push 2次 来处理了栈内的顺序
      • 进入 processChildContext 看下这个方法
        function processChildContext(
          fiber: Fiber,
          type: any,
          parentContext: Object,
        ): Object {
          const instance = fiber.stateNode;
          const childContextTypes = type.childContextTypes;
        
          // TODO (bvaughn) Replace this behavior with an invariant() in the future.
          // It has only been added in Fiber to match the (unintentional) behavior in Stack.
          // 这个属性一定是 function 才能生效
          if (typeof instance.getChildContext !== 'function') {
            if (__DEV__) {
              const componentName = getComponentName(type) || 'Unknown';
        
              if (!warnedAboutMissingGetChildContext[componentName]) {
                warnedAboutMissingGetChildContext[componentName] = true;
                warningWithoutStack(
                  false,
                  '%s.childContextTypes is specified but there is no getChildContext() method ' +
                    'on the instance. You can either define getChildContext() on %s or remove ' +
                    'childContextTypes from it.',
                  componentName,
                  componentName,
                );
              }
            }
            return parentContext;
          }
        
          let childContext;
          if (__DEV__) {
            ReactCurrentFiber.setCurrentPhase('getChildContext');
          }
          startPhaseTimer(fiber, 'getChildContext');
          childContext = instance.getChildContext(); // 执行这个 提供的api, 获取数据
          stopPhaseTimer();
          if (__DEV__) {
            ReactCurrentFiber.setCurrentPhase(null);
          }
          for (let contextKey in childContext) {
            invariant(
              contextKey in childContextTypes,
              '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
              getComponentName(type) || 'Unknown',
              contextKey,
            );
          }
          // 忽略
          if (__DEV__) {
            const name = getComponentName(type) || 'Unknown';
            checkPropTypes(
              childContextTypes,
              childContext,
              'child context',
              name,
              // In practice, there is one case in which we won't get a stack. It's when
              // somebody calls unstable_renderSubtreeIntoContainer() and we process
              // context from the parent component instance. The stack will be missing
              // because it's outside of the reconciliation, and so the pointer has not
              // been set. This is rare and doesn't matter. We'll also remove that API.
              ReactCurrentFiber.getCurrentFiberStackInDev,
            );
          }
          // 最终是两者 merge
          return {...parentContext, ...childContext};
        }
        
        • 其实,这个 processChildContext 非常简单,获取 context,合并 context
        • 这里的 parentContext 是传入的 previousContext, 这个是上面调用 pushContextProvider 时设置的全局变量 contextStackCursor.current
        • 也就是 父组件中 提供的 context 对象,最终都是为了合并
  • 总结来说,父子孙三个组件,在更新子组件的时候,先去push了一个它之前存在的这个属性

  • 因为我们不知道这个组件它是否要更新,不管它是否要更新,都要先都要执行 push 的一个操作

  • 所以,先 push 一个老的值进去再说, 然后到后面,如果发现这个组件它是要更新的,就调用这个 invalidateContextProvider 方法

  • 调用了这个方法之后, 根据传进来的 didChange,如果是 true 表示要更新,要重新去计算一个新的合并过的这个context, 即 mergedContext 给它推入到栈里面

  • 对于子组件来说,它的所有子树所获取到的context肯定是经过子组件,和上层的父组件合并的 context 了, 也就是 contextStackCursor 这里

  • 同时对于 didPerformWorkStackCursor 来说,因为 didChange 是 true,它的 current 肯定也是 true

  • 如果 didChange 是 false,这个时候不需要改变 contextStackCursor

    • 因为 push 的本来就是上面的那个值,也就是上一次计算出来的这个值
    • 就是保存在 __reactInternalMemoizedMergedChildContext 这上面的值
    • 因为它本身没有变化,不需要去改动它,而对于 didPerformWorkStackCursor 来说,需要去改变它
    • didChange 变成false,我没有更新,不能继续存之前的那个值, 因为之前的那个值可能是更新过了,它可能是 true
    • 我这次发现这个组件是不需要更新的, 要把它改成 false
    • 在一开始,这个方法就是叫做 hasContextChaned 的这个方法,它是用来判断这个组件
    • 是否可以跳过更新的一个非常关键的一个值, 如果它返回的一直是 true
    • 会导致我们每一个组件都需要去更新,而最终导致整个性能变得非常的差
    • 所以这就是 didPerformWorkStackCursor 它的一个作用
  • 老的 context api当中的 push 操作是比较复杂的,要进行一个 context 的合并这么一个过程

  • 到这里为止,将context的合并,并让它入栈

  • 注意,还有一种情况是这样的,父子孙三层组件,有一个子组件没有儿子组件

  • 也就是有多个子组件,其中有的子组件没有下层组件,这时候这类子组件拿到的是父组件原来的,而非合并过的

  • 看下具体的代码处理, 比如说我们随便挑一个 class component,更新过程当中调用的方法, 如 updateClassInstance 这个方法

    // Invokes the update life-cycles and returns false if it shouldn't rerender.
    function updateClassInstance(
      current: Fiber,
      workInProgress: Fiber,
      ctor: any,
      newProps: any,
      renderExpirationTime: ExpirationTime,
    ): boolean {
      const instance = workInProgress.stateNode;
    
      // ... 跳过很多代码
    
      const contextType = ctor.contextType; // 注意这里
      let nextContext;
      if (typeof contextType === 'object' && contextType !== null) {
        nextContext = readContext(contextType);
      } else {
        // 注意这个 else, 这个是重点
        const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
        nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
      }
    
      // ... 跳过很多代码
    
      return shouldUpdate;
    }
    
  • 这里有一个在 ctor.contextType 在 classComponent 上面去读取这个属性

  • 注意,这里 contextType 和 contextTypes 的区别

    • 后者在应用中,我们自己的代码里用于设定接收上层属性的
    • 前者就是要去读取某一个新的 contextAPI 它的 provider 上面提供的属性
    • 也就是说可以这么设置 Child.contextType = Consumer
    • 两者共同存在,则后者老的失效
    • 也就是说,如果 孙组件使用了 新的 contextType 这个api, 后面同时定义的 contextTypes 相关的会失效
  • 对于react来说,它即将把所有context相关的东西呢都放在新的contextAPI里面

    • 所以如果我们优先使用了 contextType 这种新的 context API 的使用方式
    • 就直接默认只使用新的 context
    • 而只有在没有使用这个新的 context API 的时候才会去使用老的 context API
    • 这个时候我们就会去调用一个叫做 getUnmaskedContext(workInProgress, ctor, true)
    • 这个方法来去读取用在这一个组件上面它所对应的context属性
      function getUnmaskedContext(
        workInProgress: Fiber,
        Component: Function,
        didPushOwnContextIfProvider: boolean,
      ): Object {
        if (didPushOwnContextIfProvider && isContextProvider(Component)) {
          // If the fiber is a context provider itself, when we read its context
          // we may have already pushed its own child context on the stack. A context
          // provider should not "see" its own child context. Therefore we read the
          // previous (parent) context instead for a context provider.
          return previousContext;
        }
        return contextStackCursor.current;
      }
      
      • didPushOwnContextIfProvider 传进来的时候是 true
      • 代表是否已经 push 了自己的 contextProvider,对于我们自己是一个 contextProvider 的一个情况
      • 在调用update之前,那么肯定是已经调用过push了, 如果提供了 context,这个值就是true
      • 而后面还需要判断它是否是一个 contextProvider,如果这两个条件都符合,返回的是 previousContext
      • 这个 previousContext 就是之前在调用 push 操作的时候,即 pushContextProvider,给它赋值的这个值
      • 就比如说在更新 子组件 的过程中,执行 updateClassComponent
      • 先 push 了自己的 contextProvider, 这个时候赋值给了 previousContext
      • 它等于 push 之前的那个context,就是 父组件提供的 context
      • 对于 子组件 更新的过程当中使用的 context,肯定不能使用自己提供的这个 value
      • 子组件提供的value是给自己子树,也就是孙组件及之后使用的
      • 子组件要用context的话,应该是去读取父组件提供的 context
      • 子组件要去获取父组件提供的context,要去调用 getUnmaskedContext 这个方法
      • 而如果组件同时是一个provider,那它肯定已经push过了,所以组件必须返回 previousContext 才行
      • 同样,如果组件不是一个 contextprovider,当然没有执行过push,只需要执行当前这个cursor即可
    • 进入 getMaskedContext
      function getMaskedContext(
        workInProgress: Fiber,
        unmaskedContext: Object,
      ): Object {
        const type = workInProgress.type;
        const contextTypes = type.contextTypes;
        if (!contextTypes) {
          return emptyContextObject;
        }
      
        // Avoid recreating masked context unless unmasked context has changed.
        // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
        // This may trigger infinite loops if componentWillReceiveProps calls setState.
        const instance = workInProgress.stateNode;
        if (
          instance &&
          instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
        ) {
          return instance.__reactInternalMemoizedMaskedChildContext;
        }
      
        // 注意,这里是核心
        const context = {};
        for (let key in contextTypes) {
          context[key] = unmaskedContext[key];
        }
      
        if (__DEV__) {
          const name = getComponentName(type) || 'Unknown';
          checkPropTypes(
            contextTypes,
            context,
            'context',
            name,
            ReactCurrentFiber.getCurrentFiberStackInDev,
          );
        }
      
        // Cache unmasked context so we can avoid recreating masked context unless necessary.
        // Context is created before the class component is instantiated so check for instance.
        if (instance) {
          cacheContext(workInProgress, unmaskedContext, context);
        }
      
        return context;
      }
      
      • 这个方法是在组件的父组件中所有合并过的 context 中获得当前组件需要读取的属性
      • 也就是说当前组件需要什么指定什么,context中就返回什么
  • 以上,就是对于一个 Class Component,要使用老的contextAPI, 如何去提供这个context以及它如何去获取这个context的一个过程

  • 以上是 push 操作,那什么时候才会 pop 呢?

  • 在 completeUnitOfWork 的时候,在 packages/react-reconciler/src/ReactFiberCompleteWork.js#L540

    function completeWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderExpirationTime: ExpirationTime,
    ): Fiber | null {
      const newProps = workInProgress.pendingProps;
      // ... 跳过很多代码
      switch (workInProgress.tag) {
        // ... 跳过很多代码
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            popLegacyContext(workInProgress);
          }
          break;
        }
        // ... 跳过很多代码
      }
      // ... 跳过很多代码
    }
    
    • 对于 classComponent,如果它是一个 provider,它必须要 popLegacyContext 别名是 popContext
      // packages/react-reconciler/src/ReactFiberContext.js#L124
      function popContext(fiber: Fiber): void {
        pop(didPerformWorkStackCursor, fiber);
        pop(contextStackCursor, fiber);
      }
      
      • 就把两个 cursor 给它 pop 一下
    • 在这里回到之前的push进行一下对比
      // packages/react-reconciler/src/ReactFiberContext.js#L215
      function pushContextProvider(workInProgress: Fiber): boolean {
        // ... 跳过很多代码
        push(contextStackCursor, memoizedMergedChildContext, workInProgress);
        push(
          didPerformWorkStackCursor,
          didPerformWorkStackCursor.current,
          workInProgress,
        );
      
        return true;
      }
      
      • 这里先push的是 contextStackCursor 再push的是 didPerformWorkStackCursor
    • 回到pop, 先pop的是 didPerformWorkStackCursor 再pop的是 contextStackCursor
    • 这就是我之前说过的,push 是按哪个顺序, pop的时候,必须要反过来去做
    • 这样的话,在 context-stack 中的 valueStack 里面存储的值对应的cursor的位置才是能真正对应起来
    • 对于在 completeWork 里面,我们只需要去执行 popLegacyContext 这个操作就可以了
  • 还是用之前的 Fiber 树来举例子

  • 假设整个树形结构下面的每一个节点,它都是一个classcomponent
  • 这个时候我们更新,然后 App 和 这个 div,它都提供了 childContextTypes
  • 这个时候对于 input 这个 classcomponent,它要更新的过程中肯定要获取
  • 这个App和和div提供的context合并之后的那个对象,对App更新的过程中,它调用了push
  • 然后在div执行更新的时候,它先去获取App push 的那个 context 作为它更新的时候要用的那个context
  • 它自己也要去push一个context,push的时候要跟App提供的context进行一个合并
  • 这个时候游标已经到了第二个context了, 第一个context是App提供的, 第二个是App和和div合并的
  • 这两个值都是在stack栈里面有存着的,只不过现在的 contextStackCursor 这个游标
  • 指向的是div它合并过之后的那个context,这个context是提供给Input渲染更新的时候,它要获取的那个值
  • 就这么一层一层下来之后,到最后更新 input 这个节点的时候,它拿到的context是上面这三个组件,它合并过的 context
  • 对于List的更新,它不需要Input合并进去,它只需要App和div它们合并之后的那个 context 对象
  • 这个就是我们在 completeUnitOfWork 的时候,要去pop这个stack
    • 在input节点执行 completeUnitOfWork 的时候
      • 要 pop input节点及以上提供的context合并之后的一个值
      • 对应 cursor 指向 App, div, Input 合并后的 context
    • 在Input节点执行 completeUnitOfWork 的时候
      • 要 pop Input节点及以上提供的context合并之后的一个值
      • 对应 cursor 指向 App 和 div 合并后的 context
    • 在Input发现sibling节点 List,要对这个兄弟节点执行 beginWork,也就是更新的流程
    • 这个时候 List 拿到的这个 context,也就是 App 和 div合并之后的节点
      • 所以这就是在 beginWork 的时候,要对 classcomponent 进行一个push的操作
      • 等到要去 completeUnitOfWork 的时候,执行到每个节点,要执行对应的pop
      • 所以这个时候就可以对节点 push 和 pop 的位置可以一一对应起来
      • 这样的话就不会造成整个stack里面的这个顺序混淆
  • 这就是对于 childContextTypes 这种context提供的方式的一个使用的过程
  • 这个 API 最终被删除了,因为前面谈到的性能问题,以上是它的整体原理
AI在电商中的搜索优化 自拟标题 《AI技术助力电商搜索优化:深度解析一线大厂面试题及算法编程实践》 概述 随着人工智能技术的不断发展,AI在电商搜索优化中的应用日益广泛。本文将围绕“AI在电商中的搜索优化”这一主题,介绍国内头部一线大厂如阿里巴巴、百度、腾讯、字节跳动等在面试中常见的AI相关面试题和算法编程题,并提供详尽的答案解 阅读详情

相关推荐

Anolis 8系统下,手把手搞定Nginx、Redis、JDK8和Tomcat9的完整安装与配置(含开机自启)

本文详细介绍了在Anolis 8系统下搭建Java Web全栈环境的完整流程,包括JDK8、Tomcat9、Nginx和Redis的安装与配置,并实现系统级服务管理。通过深入每个组件的配置细节,揭示服务间的协同关系,最终打造一个生产级可用的标准化环境。

weixin_42556197的博客 160

React的Context API及对旧版Context问题的思考

文档上说:Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。 文档上说:Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。 文档上说:不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。 官方文档最后说:请谨慎...

daihaoxin的专栏 640

Node框架之NestJS入门学习

文章主要分为三部分:1 NestJS 与Egg对比2 NestJS 基础知识介绍3 使用NestJS快速搭建应用,实现CURD操作e-link文档Egg官网NestJS 官网NestJS中文文档(非官方)

十九万里的博客 4742

react源码解析6.legacy模式和concurrent模式

react源码解析6.legacy模式和concurrent模式 视频课程(高效学习):进入课程 课程目录: 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.conc

bemystery的博客 513

Centos7.6操作系统安装+Bond配置(600GB磁盘版本)

1.安装前准备工作 准备centos7.6操作系统镜像 CentOS-7-x86_64-DVD-1810.iso 服务器安装一般都是在BMC界面操作,所以安装前需要在BMC界面挂载好操作系统镜像 重启服务器后自动进入安装界面。 华为大数据集群安装操作系统配置

小时光 6013

S7-1200 数据日志

数据日志文件按照标准 CSV 格式存储在 S7-1200 CPU 装载存储器或 S7-1200 SIMATIC 存储卡中。分别可通过 PLC Web 服务器或将数据文件传送到 PC 进行管理、查看。 一、测试目的: 下面以数据日志文件存储在 S7-1200 CPU 装载存储器中,使用PLC Web 服务器进行管理为例,实现下述功能: (1)执行“DataLogging”指令将产品型号“Type”,长度“Length”,宽度“Width”三个变量值写入到数据日志文件; (2)当写入的条数达到设定的3条

qq_43029096的博客 2542

ant design 使用过程中遇到的问题

Failed to parse source map: 'webpack://antd/./components/config-provider/style/index.less' URL is not supported

FenceRain的博客 3475

cellchat安装

cellchat

m0_65188455的博客 5449

react-context旧版API

我觉得我在放屁但是我还得说下,上下文只能是先代创建后代使用,非先后代关系不可使用。

yuey0809的博客 430

YOLO26优化:注意力魔改 | 用于微小目标检测的上下文增强和特征细化网络ContextAggregation,助力小目标检测,暴力涨点

用于微小目标检测的上下文增强和特征细化网络ContextAggregation,助力小目标检测

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 492

智能手表竞品分析与市场调研报告

本报告旨在通过调研当前主流智能手表产品的功能特点、市场表现、用户反馈、定价策略等方面,辅助我司即将立项的Aurora Watch S1产品在功能定义、目标用户、定价区间、差异化竞争等方面做出科学决策。 调研范围包括国内外中端价位主流品牌(¥199~¥999),重点关注小米、华为、荣耀、Amazfit、Redmi、Haylou 等品牌的热销型号。

hoony123的博客 7万+

react性能优化|bailout策略

{/* 一个长列表 */}

qq_42996469的博客 880

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

react报错:Legacy context API has been detected within a strict-mode tree

react报错:Legacy context API has been detected within a strict-mode tree

wejack的专栏 849

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

为什么你应该放弃老的React Context API改用新的Context API

React16.3发布了新的Context API,并且已经确认了将在下一个版本废弃老的Context API。所以大家更新到新的Context API是无可厚非的事情。而这篇文章会从原理的角度为大家分析为什么要用新的API--不仅仅是因为React官方要更新,毕竟更新了你也可以用16版本的React来使用老的API--而是因为新的API...

weixin_33888907的博客 475

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Legacy context API has been detected within a strict-mode tree.

最近写React后台项目的时候系统总是给我报这个警告: index.js:1 Warning: Legacy context API has been detected within a strict-mode tree. The old API will be supported in all 16.x releases, but applications using it should migrate to the new version. Please update the following

ProgrammingWhite的博客 4427

Fix signatures do not match the previously installed version

报错信息FAILURE: Build failed with an exception.* What went wrong: Execution failed for task ':app:installDebug'. > com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: Fai

Wang的专栏 2万+

在react中 实现 base64的图片转换成formdata格式并实现上传功能

如果不想要服务端实现处理base64格式的图片的转换,那么在浏览器端来实现,部分属于伪代码,仅作为开发参考,请勿直接拷贝使用

Wang的专栏 8836

快速解决gradle下载慢的问题的小技巧

在ionic run 或build 或clean命令的时候,如果没有下载过gradle, 需要重新下载一次,而通过CLI的方式下载很慢,即使科学上网后,下载还是存在一些问题。这里总结一下,快速的下载安装方式。 步骤 本地安装 http-server 包, 通过 $ npm i -g http-server 的方式安装到全局 在官网上下载自己的版本,目前自带的版本是 2.14.1,其他版本可能在执行过

Wang的专栏 6714

React 实现 2D 画板功能

在移动端或者PC端我们来模拟画板的实现, 支持清除和导出功能,此处简单实现,仅作开发参考。 效果演示 主要代码演示 /* 此模块用于实现签名绘图功能 */ // 需要用到的变量定义 let clickX = new Array(); let clickY = new Array(); let clickDrag = new Array(); let paint; let point...

Wang的专栏 4325

Fix babel-preset-react-native TransformError

错误环境新版本存在不兼容,在运行 react-native run-android 的时候,发生的错误分析: babel-preset-react-native version : @3.0.0 $ react-native -v :react-native-cli: 2.0.1 react-native: 0.47.1$node -v : v7.8.0 $ npm -v :4.4.4 解决方案

Wang的专栏 2800

React18原理: 再聊Fiber架构下的时间分片

react的任务可以被打断,其实就是基于时间分片的,人眼最高能识别的帧数不超过30帧,电影的帧数差不多是在24,浏览器的帧率一般来说是60帧,也就是每秒60个画面, 平均一个画面大概是16.5毫秒左右,浏览器正常的工作流程是运算渲染,运算,渲染运算渲染,在浏览器里面一个运算,加上一个渲染就是一帧

Wang的专栏 2796

在react项目中修改webpack中本地服务器架设的目录

一个产品的上线往往会有很多的问题,现在就出现一个需求,我们是在服务器下的某个目录进行发布,而不是根服务器,服务端不想为一个项目配置ip地址,而在react项目中,默认是走的根路径,现在我们就开始修改一下它。 前提说明 如果我们需要修改手动配置webpack, 那么根据现在的版本, 我们只能执行 npm run eject, 之后就可以着手修改了。 webpack中与需求相关的...

Wang的专栏 2397

React18源码: React调度中的3种优先级类型和Lane的位运算

可以看得出来lane&-lane的结果是SyncLane,所以通过lane&-lane就能分离出最高优先级的任务。即A(cpu)>B(IO)>C(cpu).此时的需求需要将任务B从group中分离出来,先处理cpu任务A和C。在新版本的React中,还有一个新特性,就是render阶段可能被中断,在这个期间会产生一个更高优先级的任务。如上就是通过 lanes & -lanes 分离出最高优先级的任务的,我们来看一下具体的流程。也即是说lane的代表的数值越小,此次更新的优先级就越大。

Wang的专栏 2394

React18原理: 核心包结构与两大工作循环

react中的工作循环,这是它的一个最顶层的一个流程图,在这张图里面,有两个核心的大循环,第一大循环是 react-reconciler 提供的,它的核心是构建fiber树,生成任务,第二大循环是 scheduler 提供的, 它的核心是任务调度(处理任务的优先级),react的原理实际上就是两大工作循环

Wang的专栏 2356
上一篇: React16源码: React中context-stack的源码实现
下一篇: React16源码: React中NewContext的源码实现
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值