【React用到的一些算法】游标和栈

前言

这一篇介绍的是React用到的一些算法。

正文

在遍历fiber树的过程中还维护了游标和栈。

在beginWork completeWork的过程中switch catch到updateHostComponent第一行可以看到

js 复制代码
function updateHostComponent(current, workInProgress, renderLanes) {
  pushHostContext(workInProgress);
  ...
}

completeWork是对应的popHostContext。

总是在fiber节点的beginWork/completeWork,在updateHostComponent等的第一行出现。

概念和组成

"游标"是指向栈中元素的一个指针,例如指向了栈顶元素。

2个公共的栈 + 5个游标组成。

ini 复制代码
var valueStack = [];//1.包含上下文Context、上下文Context是否change、fiber、嵌套信息...混合了所有的游标类型
var fiberStack; //2.只有fiber类型

var contextStackCursor = createCursor(emptyContextObject);
var didPerformWorkStackCursor = createCursor(false);


var contextStackCursor$1 = createCursor(NO_CONTEXT);
var contextFiberStackCursor = createCursor(NO_CONTEXT);
var rootInstanceStackCursor = createCursor(NO_CONTEXT);

//初始化
var emptyContextObject = {};
var previousContext = emptyContextObject;

var NO_CONTEXT = {};

{
  fiberStack = [];//初始化fiberStack
}
var index = -1;

function createCursor(defaultValue) {
  return {
    current: defaultValue
  };
}

游标分类:

1. 旧版的Context API用到的游标
  • contextStackCursor 把Context作为上下文对象
  • didPerformWorkStackCursor 把Context是否变化了作为上下文对象

入栈函数

  • pushContextProvider 设置contextStackCursor和didPerformWorkStackCursor
  • invalidateContextProvider 设置contextStackCursor和didPerformWorkStackCursor
  • pushTopLevelContextObject 设置HostRoot的contextStackCursor和didPerformWorkStackCursor
2. dom嵌套信息和这个dom对应Fiber
  • contextStackCursor$1 把dom嵌套信息 作为上下文

current不能跨级,每一次都是新的,表示当前html标签的直接父标签

pTagInButtonScope...等 可以跨级 ,表示当前标签的祖先是否有p标签,一直等到当前节点也是p等才更新为当前p

updatedAncestorInfo计算当前html标签的ancestorInfo,

yaml 复制代码
//祖先嵌套信息
nextContext = {
  ancestorInfo:{
    aTagInScope: null
    buttonTagInScope : null
    current: {tag: 'div'}
    dlItemTagAutoclosing: null
    formTag: null
    listItemTagAutoclosing: null
    nobrTagInScope: null
    pTagInButtonScope: null
  }
  namespace: "http://www.w3.org/1999/xhtml"
}

push(contextStackCursor$1, nextContext, fiber);
  • contextFiberStackCursor是和contextStackCursor$1 一起用的,是当前的fiber节点

push(contextFiberStackCursor, fiber, fiber);很明显,fiber给了cursor

入栈函数

  • pushHostContext,HostComponent类型的Fiber节点使用,设置contextStackCursor$1和contextFiberStackCursor
3. HostRoot和HostPortal类型的Fiber节点,它们所对应的dom节点对象
  • rootInstanceStackCursor,HostRoot和HostPortal类型的Fiber节点,所对应的dom节点对象。这表明是根容器,如div#root,还要保存根容器。这侧面表明可以有多层根容器。

比如:

js 复制代码
- HostRoot (div#root)
    └─ HostPortal (div#a)
        └─ HostPortal (div#b)
            └─ HostPortal (div#c)

ReactDOM.createPortal(, document.getElementById("a"))

4. 综合设置游标-设置3个游标

HostPortal 、pushHostRootContext,需要综合设置3个游标

设置rootInstanceStackCursor 和 contextFiberStackCursor和contextStackCursor$1

  • 入栈函数pushHostContainer
5. 设置全部游标

HostRoot类型的Fiber节点需要一次性设置全部游标

  • 入栈函数pushHostRootContext

6个入栈函数

  1. 旧版的Context API用到的游标 contextStackCursor和didPerformWorkStackCursor
ini 复制代码
var emptyContextObject = {};
function pushContextProvider(workInProgress) {
  {
    var instance = workInProgress.stateNode; 
    
    var memoizedMergedChildContext = instance && instance.__reactInternalMemoizedMergedChildContext || emptyContextObject; 

    previousContext = contextStackCursor.current;
    push(contextStackCursor, memoizedMergedChildContext, workInProgress);
    push(didPerformWorkStackCursor, didPerformWorkStackCursor.current, workInProgress);
    return true;
  }
}
scss 复制代码
function invalidateContextProvider(workInProgress, type, didChange) {
  {
    var instance = workInProgress.stateNode;

    if (!instance) {
      throw new Error('Expected to have an instance by this point. ' + 'This error is likely caused by a bug in React. Please file an issue.');
    }

    if (didChange) {
      // 合并上下文对象,类似于assign({},obj1,obj2}
      var mergedContext = processChildContext(workInProgress, type, previousContext);
      instance.__reactInternalMemoizedMergedChildContext = mergedContext; 
      // 先把pushContextProvider的取出来
      pop(didPerformWorkStackCursor, workInProgress);
      pop(contextStackCursor, workInProgress); 
      
      push(contextStackCursor, mergedContext, workInProgress);
      push(didPerformWorkStackCursor, didChange, workInProgress);
    } else {
      pop(didPerformWorkStackCursor, workInProgress);
      push(didPerformWorkStackCursor, didChange, workInProgress);
    }
  }
}
scss 复制代码
function pushTopLevelContextObject(fiber, context, didChange) {
  {
    if (contextStackCursor.current !== emptyContextObject) {
      throw new Error('Unexpected context found on stack. ' + 'This error is likely caused by a bug in React. Please file an issue.');
    }

    push(contextStackCursor, context, fiber);
    push(didPerformWorkStackCursor, didChange, fiber);
  }
}
  1. dom嵌套信息和这个dom对应Fiber contextStackCursor$1和contextFiberStackCursor
scss 复制代码
function pushHostContext(fiber) {
  var rootInstance = requiredContext(rootInstanceStackCursor.current);
  var context = requiredContext(contextStackCursor$1.current);
  var nextContext = getChildHostContext(context, fiber.type); 
  
  // Don't push this Fiber's context unless it's unique.
  if (context === nextContext) {
    return;
  } 
  
  push(contextFiberStackCursor, fiber, fiber);
  push(contextStackCursor$1, nextContext, fiber);
}
  1. HostPortal 、pushHostRootContext 需要综合设置3个游标
scss 复制代码
function pushHostContainer(fiber, nextRootInstance) {
  push(rootInstanceStackCursor, nextRootInstance, fiber); 
  
  push(contextFiberStackCursor, fiber, fiber); 
  
  push(contextStackCursor$1, NO_CONTEXT, fiber);
  var nextRootContext = getRootHostContext(nextRootInstance); 
  
  pop(contextStackCursor$1, fiber);
  push(contextStackCursor$1, nextRootContext, fiber);
}
  1. HostRoot 设置全部5个游标,rootInstanceStackCursor...
scss 复制代码
function pushHostRootContext(workInProgress) {
  var root = workInProgress.stateNode;

  if (root.pendingContext) {
    pushTopLevelContextObject(workInProgress, root.pendingContext, root.pendingContext !== root.context);
  } else if (root.context) {
    pushTopLevelContextObject(workInProgress, root.context, false);
  }

  pushHostContainer(workInProgress, root.containerInfo);
}

为什么要栈、为什么要游标

因为处在现在的值时需要知道上一个值。

  1. 旧版Context的嵌套
  2. dom嵌套的信息
  3. 根容器嵌套的信息
对应游标:
  1. 最新的Context的值
  2. 当前层dom的嵌套信息
  3. 当前的根容器dom节点,例如div#root、div#modal-root

ReactDOM.createPortal(, document.getElementById("modal-root"))

  • 例如:
scss 复制代码
A(Provider)
 └─ B(Provider)
     └─ C(Consumer)
css 复制代码
            valueStack           contextStackCursor.current
A push       {}                  {}+A
B push       {}+A                {}+A+B
C render     {}+A                {}+A+B   ← 与 B 相同

注意Consumer是消费上下文,C组件没有自己的Context,所以没有push C Context。

应该和B保持一样,因为C没有push

  • 例如:

React completeWork归阶段需要校验dom嵌套是否合法validateDOMNesting

ini 复制代码
function getHostContext() {
  var context = requiredContext(contextStackCursor$1.current);
  return context;
}

var currentHostContext = getHostContext();//contextStackCursor$1.current

var hostContextDev = hostContext;
//validateDOMNesting需要知道上下文,validateDOMNesting第三个参数
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);

push和参数

cursor分别是contextStackCursor、didPerformWorkStackCursor...5个游标

value是nextContext fiber didChange等,如祖先信息对象、Provider-Context上下文对象

我们在push的时候提供的第二个参数value是第一个参数游标的最新的值

scss 复制代码
function push(cursor, value, fiber) {
  index++;
  valueStack[index] = cursor.current;//旧的

  {
    fiberStack[index] = fiber;
  }

  cursor.current = value;//最新的
}

push(contextStackCursor, context, fiber);//context是contextStackCursor的值
push(didPerformWorkStackCursor, didChange, fiber);//cursor是didPerformWorkStackCursor,value是didChange

valueStack看起来非常的"乱"

混和了所有游标类型的值,不能预测里面的值,无法仅从 valueStack[i] 知道它属于哪个上下文,但是push/pop总是成对出现的。

栈和成对出现的push/pop,正确的维护了游标的值,出栈的时候更新游标为上一个上下文。

这样处在当前位置,可以直接从游标取到当前位置的上下文。

scss 复制代码
function pop(cursor, fiber) {
  //栈空了
  if (index < 0) {
    {
      error('Unexpected pop.');
    }
    return;
  }
  {
    //通过验证 游标对应fiber 和 fiberStack 是不是对应,确认push和pop是成对的
    if (fiber !== fiberStack[index]) {
      error('Unexpected Fiber popped.');
    }
  }
  //valueStack游标出栈
  cursor.current = valueStack[index]; //更新为上一层上下文
  valueStack[index] = null;
  {
    //faberStack游标出栈
    fiberStack[index] = null;
  }
  index--;
}

总结

只要知道游标和栈的核心用法:push和pop成对出现,栈保存上一个的值,游标保存最新的值,pop更新游标的值,使用游标就能得到正确的值。到了各种具体的使用场景都是一样的:不论是5种游标还是6种游标。

相关推荐
小高0072 小时前
🔍说说对React的理解?有哪些特性?
前端·javascript·react.js
博笙困了3 小时前
AcWing学习——双指针算法
c++·算法
moonlifesudo3 小时前
322:零钱兑换(三种方法)
算法
江城开朗的豌豆4 小时前
从生命周期到useEffect:我的React函数组件进化之旅
前端·javascript·react.js
江城开朗的豌豆4 小时前
React组件传值:轻松掌握React组件通信秘籍
前端·javascript·react.js
遂心_19 小时前
深入理解 React Hook:useEffect 完全指南
前端·javascript·react.js
NAGNIP21 小时前
大模型框架性能优化策略:延迟、吞吐量与成本权衡
算法
前端小书生21 小时前
React 组件渲染
前端·react.js
美团技术团队1 天前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法