React Fiber 树上下文
Id 是基于 32 的字符串,其二进制表示对应于树中节点的位置。
每当树分叉成多个子节点时,我们会在序列的左侧添加额外的位,表示子节点在当前子节点层级中的位置。
00101 00010001011010101
╰─┬─╯ ╰───────┬───────╯
Fork 5 of 20 Parent id
前导的 0 很重要。在上面的例子中,你只需要 3 位来表示槽 5。但是,你需要 5 位来表示当前层级的所有分支,因此我们必须考虑末尾的空位。
出于同样的原因,槽位是从 1 开始索引的,而不是从 0 开始索引的。否则,一个层级中的零号 ID 将无法与其父级区分开。
如果一个节点只有一个子节点,并且没有生成 id(即不包含 useId 钩子),那么我们就不需要在序列中分配任何空间。它被视为透明的间接引用。例如,这两个树会生成相同的 id:
<>
<>
<Indirection>
<A />
<A /> <B />
</Indirection>
</>
<B />
</>
但是,我们不能跳过任何生成 id 的节点。否则,一个不分叉的父 id 将无法与其子 id 区分开来。例如,这棵树没有分叉,但父节点和子节点必须有不同的 id。
序列的大小可能会超过 32 位,这是位运算的最大大小。当这种情况发生时,我们通过将 ID 的右侧部分转换为字符串并存储在溢出变量中来腾出更多空间。我们使用 32 进制字符串表示,因为 32 是 toString() 支持的最大 2 的幂。我们希望进制数大,以便生成的 ID 更紧凑;我们希望进制数是 2 的幂,因为每 log2(进制) 位对应一个字符,也就是说每 log2(32) = 5 位。这意味着我们可以每次从末尾去掉 5 位而不影响最终结果。
一、作用
二、是否为子进程
export function isForkedChild(workInProgress: Fiber): boolean {
warnIfNotHydrating();
return (workInProgress.flags & Forked) !== NoFlags;
}
三、获取指定级别的分支
export function getForksAtLevel(workInProgress: Fiber): number {
warnIfNotHydrating(); // + 本文档实现
return treeForkCount;
}
四、获取树 Id
export function getTreeId(): string {
const overflow = treeContextOverflow;
const idWithLeadingBit = treeContextId;
const id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit);
return id.toString(32) + overflow;
}
五、添加树叉
export function pushTreeFork(
workInProgress: Fiber,
totalChildren: number,
): void {
// This is called right after we reconcile an array (or iterator) of child
// fibers, because that's the only place where we know how many children in
// the whole set without doing extra work later, or storing addtional
// information on the fiber.
//
// 这会在我们对子节点数组(或迭代器)的 fiber 进行协调后立即调用,
// 因为这是唯一能在不做额外工作的情况下知道整个集合中有多少子节点的地方,
// 或者不需在 fiber 上存储额外信息。
//
// That's why this function is separate from pushTreeId — it's called during
// the render phase of the fork parent, not the child, which is where we push
// the other context values.
//
// 这就是为什么这个函数与 pushTreeId 是分开的——它在 fork 父节点的渲染阶段调用,
// 而不是在子节点中调用,子节点里我们会推送其他上下文值。
//
// In the Fizz implementation this is much simpler because the child is
// rendered in the same callstack as the parent.
//
// 在 Fizz 实现中,这要简单得多,因为子组件与父组件在同一个调用栈中渲染。
//
// It might be better to just add a `forks` field to the Fiber type. It would
// make this module simpler.
//
// 也许直接给 Fiber 类型添加一个 `forks` 字段会更好。
// 这会让这个模块更简单。
warnIfNotHydrating();
forkStack[forkStackIndex++] = treeForkCount;
forkStack[forkStackIndex++] = treeForkProvider;
treeForkProvider = workInProgress;
treeForkCount = totalChildren;
}
六、添加树 Id
export function pushTreeId(
workInProgress: Fiber,
totalChildren: number,
index: number,
) {
// + 本文档实现
warnIfNotHydrating();
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextProvider = workInProgress;
const baseIdWithLeadingBit = treeContextId;
const baseOverflow = treeContextOverflow;
// The leftmost 1 marks the end of the sequence, non-inclusive. It's not part
// of the id; we use it to account for leading 0s.
//
// 最左边的 1 标记序列的结束,不包含在内。它不是 id 的一部分;我们用它来处理前导 0。
// + 本文档实现
const baseLength = getBitLength(baseIdWithLeadingBit) - 1;
const baseId = baseIdWithLeadingBit & ~(1 << baseLength);
const slot = index + 1;
// + 本文档实现
const length = getBitLength(totalChildren) + baseLength;
// 30 is the max length we can store without overflowing, taking into
// consideration the leading 1 we use to mark the end of the sequence.
//
// 30 是我们在不溢出的情况下可以存储的最大长度,考虑到我们用来标记序列结尾的前导 1。
if (length > 30) {
// We overflowed the bitwise-safe range. Fall back to slower algorithm.
// This branch assumes the length of the base id is greater than 5; it won't
// work for smaller ids, because you need 5 bits per character.
//
// 我们超出了按位安全范围。退回到较慢的算法。
// 这一分支假设基础 ID 的长度大于 5;对于较小的 ID 它不起作用,
// 因为每个字符需要 5 位。
//
// We encode the id in multiple steps: first the base id, then the
// remaining digits.
//
// 我们将 ID 分多步编码:首先是基础 ID,然后是剩余的数字。
//
// Each 5 bit sequence corresponds to a single base 32 character. So for
// example, if the current id is 23 bits long, we can convert 20 of those
// bits into a string of 4 characters, with 3 bits left over.
//
// 每个 5 位序列对应一个单独的 Base32 字符。例如,如果当前的 ID 有 23 位长,我们
// 可以将其中的 20 位转换为 4 个字符的字符串,剩下 3 位。
//
// First calculate how many bits in the base id represent a complete
// sequence of characters.
//
// 首先计算基础 ID 中有多少位表示一个完整的字符序列。
const numberOfOverflowBits = baseLength - (baseLength % 5);
// Then create a bitmask that selects only those bits.
// 然后创建一个只选择这些位的位掩码。
const newOverflowBits = (1 << numberOfOverflowBits) - 1;
// Select the bits, and convert them to a base 32 string.
// 选择这些位,并将它们转换为 base32 字符串。
const newOverflow = (baseId & newOverflowBits).toString(32);
// Now we can remove those bits from the base id.
// 现在我们可以从基本 ID 中移除那些部分。
const restOfBaseId = baseId >> numberOfOverflowBits;
const restOfBaseLength = baseLength - numberOfOverflowBits;
// Finally, encode the rest of the bits using the normal algorithm. Because
// we made more room, this time it won't overflow.
//
// 最后,使用常规算法对剩余的位进行编码。因为我们腾出了更多空间,这次不会溢出。
// + 本文档实现
const restOfLength = getBitLength(totalChildren) + restOfBaseLength;
const restOfNewBits = slot << restOfBaseLength;
const id = restOfNewBits | restOfBaseId;
const overflow = newOverflow + baseOverflow;
treeContextId = (1 << restOfLength) | id;
treeContextOverflow = overflow;
} else {
// Normal path
// 正常路径
const newBits = slot << baseLength;
const id = newBits | baseId;
const overflow = baseOverflow;
treeContextId = (1 << length) | id;
treeContextOverflow = overflow;
}
}
七、推送物化树ID
export function pushMaterializedTreeId(workInProgress: Fiber) {
// + 本文档实现
warnIfNotHydrating();
// This component materialized an id. This will affect any ids that appear
// in its children.
// 这个组件会生成一个 id。这会影响其子组件中出现的任何 id。
const returnFiber = workInProgress.return;
if (returnFiber !== null) {
const numberOfForks = 1;
const slotIndex = 0;
// + 本文档实现
pushTreeFork(workInProgress, numberOfForks);
// + 本文档实现
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
八、弹出树上下文
export function popTreeContext(workInProgress: Fiber) {
// Restore the previous values.
// 恢复以前的值。
// This is a bit more complicated than other context-like modules in Fiber
// because the same Fiber may appear on the stack multiple times and for
// different reasons. We have to keep popping until the work-in-progress is
// no longer at the top of the stack.
//
// 这比 Fiber 中的其他类似上下文的模块要复杂一些
// 因为同一个 Fiber 可能会在堆栈中出现多次,并且原因各不相同。
// 我们必须不断弹出,直到正在进行的工作不再位于堆栈顶部。
while (workInProgress === treeForkProvider) {
treeForkProvider = forkStack[--forkStackIndex];
forkStack[forkStackIndex] = null;
treeForkCount = forkStack[--forkStackIndex];
forkStack[forkStackIndex] = null;
}
while (workInProgress === treeContextProvider) {
treeContextProvider = idStack[--idStackIndex];
idStack[idStackIndex] = null;
treeContextOverflow = idStack[--idStackIndex];
idStack[idStackIndex] = null;
treeContextId = idStack[--idStackIndex];
idStack[idStackIndex] = null;
}
}
九、获取挂起树上下文
export function getSuspendedTreeContext(): TreeContext | null {
// + 本文档实现
warnIfNotHydrating();
if (treeContextProvider !== null) {
return {
id: treeContextId,
overflow: treeContextOverflow,
};
} else {
return null;
}
}
十、恢复挂起的树上下文
export function restoreSuspendedTreeContext(
workInProgress: Fiber,
suspendedContext: TreeContext,
) {
// + 本文档实现
warnIfNotHydrating();
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextId = suspendedContext.id;
treeContextOverflow = suspendedContext.overflow;
treeContextProvider = workInProgress;
}
十一、变量
1. en
// TODO: Use the unified fiber stack module instead of this local one?
// Intentionally not using it yet to derisk the initial implementation, because
// the way we push/pop these values is a bit unusual. If there's a mistake, I'd
// rather the ids be wrong than crash the whole reconciler.
//
// 待办事项:使用统一的 fiber 栈模块,而不是这个本地的?
// 暂时没有使用它,以降低初始实现的风险,因为我们推入/弹出这些值的方式有点不寻常。如果出现错误,
// 我宁愿 ID 出错,也不愿整个协调器崩溃。
const forkStack: Array<any> = [];
let forkStackIndex: number = 0;
let treeForkProvider: Fiber | null = null;
let treeForkCount: number = 0;
const idStack: Array<any> = [];
let idStackIndex: number = 0;
let treeContextProvider: Fiber | null = null;
let treeContextId: number = 1;
let treeContextOverflow: string = '';
十二、工具
1. 如果未在水合则警告
备注
getIsHydrating()在 ReactFiberHydrationContext 中实现
function warnIfNotHydrating() {
if (__DEV__) {
if (!getIsHydrating()) {
console.error(
'Expected to be hydrating. This is a bug in React. Please file ' +
'an issue.',
);
}
}
}
2. 获取最高有效位
function getLeadingBit(id: number) {
return 1 << (getBitLength(id) - 1);
}
3. 获取位长度
备注
clz32()由 clz32 实现
function getBitLength(number: number): number {
return 32 - clz32(number);
}