Video Course (Efficient Learning) : [Enter the course]

(xiaochen1024.com/series/60b1…).

The context diagram

cursor/valueStack

React in a source valueStack and valueCursor used to record the context of history and the current context, another didPerformWorkStackCursor used to say there are any changes in the current context

//ReactFiberNewContext.new.js
const valueCursor: StackCursor<mixed> = createCursor(null);
Copy the code
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
Copy the code
//ReactFiberStack.new.js
const valueStack: Array<any> = [];
Copy the code
function pushProvider(providerFiber, nextValue) {
  varcontext = providerFiber.type._context; { push(valueCursor, context._currentValue, providerFiber); context._currentValue = nextValue; }}Copy the code
function popProvider(providerFiber) {
  var currentValue = valueCursor.current;
  pop(valueCursor, providerFiber);
  varcontext = providerFiber.type._context; { context._currentValue = currentValue; }}Copy the code
  • PushProvider is executed when updateContextProvider is called in the Render phase, pushing the new value into valueStack

  • PopProvider is performed when the completeWork is called in the COMMIT phase, pop the context out of the top of the stack,

In the render and Commit phases, we used depth-first traversal of the nodes. If it involves reading state across layers, we need to pass our props layer by layer. So we can use a stack to record our context, pushProvider in the Render phase, popProvider in the commit phase, and take the current value according to valueCursor at each specific level

createContext

export function createContext<T> (defaultValue: T, calculateChangedBits: ? (a: T, b: T) => number,) :ReactContext<T> {
  if (calculateChangedBits === undefined) {// We can pass in a function that evaluates bit
    calculateChangedBits = null;
  } else {
    / /...
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,// Compute a function that changes value
    _currentValue: defaultValue,// Dom environment value
    _currentValue2: defaultValue,// Value of the art environment
    _threadCount: 0.Provider: (null: any),
    Consumer: (null: any),
  };

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



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


  return context;
}

/ / sample
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;
});

Copy the code

As you can see in the simplified createContext, the relationship between context and Provider and Consumer looks like this:

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

useContext

UseContext calls readContext, which creates a Dependce in the dependencies list of the current Fiber

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

    / / generated resolvedObservedBits
    if (typeofobservedBits ! = ='number' || observedBits === MAX_SIGNED_31_BIT_INT) {
      lastContextWithAllBitsObserved = context;
      resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
    } else {
      resolvedObservedBits = observedBits;
    }

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

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

      lastContextDependency = contextItem;
      currentlyRenderingFiber.dependencies = {// list of dependencies
        lanes: NoLanes,
        firstContext: contextItem,
        responders: null
      };
    } else {
      lastContextDependency = lastContextDependency.next = contextItem;// Add to the dependencies list}}return  context._currentValue ;
}
Copy the code

provider/customer

The updateContextProvider is called in the Render phase, noting a few key steps

  • PushProvider: adds the current context to valueStack

  • CalculateChangedBits: UseContext can set observedBits, otherwise MAX_SIGNED_31_BIT_INT, which is 31 bits 1 for calculating changedBits, This calculation of whether or not the context has changed occurs in the calculateChangedBits function, which improves performance after the context has changed

  • BailoutOnAlreadyFinishedWork/propagateContextChange: If changedBits does not change bailoutOnAlreadyFinishedWork logic, skip the update of the current node, if change the execution 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) {// The context has not changed
      if(oldProps.children === newProps.children && ! hasContextChanged()) {returnbailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); }}else {/ / the context has changedpropagateContextChange(workInProgress, context, changedBits, renderLanes); }}var newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}
Copy the code
function calculateChangedBits(context, newValue, oldValue) {
  if (objectIs(oldValue, newValue)) {
		// No change
    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; }}/ / sample
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;
});
Copy the code
function propagateContextChange(workInProgress, context, changedBits, renderLanes) {
  var fiber = workInProgress.child;

  if(fiber ! = =null) {
    fiber.return = workInProgress;// If fiber does not exist, find the parent node
  }

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

    var list = fiber.dependencies;

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

      while(dependency ! = =null) {// run the list of dependencies
        if(dependency.context === context && (dependency.observedBits & changedBits) ! = =0) {
					/ / a change
          if (fiber.tag === ClassComponent) {
            // Create a new update
            var update = createUpdate(NoTimestamp, pickArbitraryLane(renderLanes));
            update.tag = ForceUpdate; 
            enqueueUpdate(fiber, update);
          }

          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);// Merge priorities
          var alternate = fiber.alternate;

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

          scheduleWorkOnParentPath(fiber.return, renderLanes); // Update the priority of the ancestor node

          list.lanes = mergeLanes(list.lanes, renderLanes); 
          break; } dependency = dependency.next; }}/ /...
      nextFiber = fiber.sibling;
    } else {
      nextFiber = fiber.child;
    }
    / /...fiber = nextFiber; }}Copy the code

The key code for updateContextConsumer is as follows: Execute prepareToReadContext to determine if the priority is sufficient to add the current Render, and readContext takes the value of the current context

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;
}
Copy the code