前言
这一篇介绍的是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个入栈函数
- 旧版的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);
}
}
- 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);
}
- 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);
}
- 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);
}
为什么要栈、为什么要游标
因为处在现在的值时需要知道上一个值。
- 旧版Context的嵌套
- dom嵌套的信息
- 根容器嵌套的信息
对应游标:
- 最新的Context的值
- 当前层dom的嵌套信息
- 当前的根容器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种游标。