We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
react17 放弃了之前的 expirationTime 而启用了 lane 模型,故而在原来 16 的基础上又产生了更多的二进制运算,在接下来的一段时间我打算把这些二进制运算都整明白了、
在 react 的更新中,executionContext按照字面意思即为执行上下文,executionContext 的默认值是 0NoContext, 此后的 executionContext 的更新中,都是与其他不同的上下文以按位或的运算的方式进行更新的,react17 里的不同上下文有如下 8 种:
executionContext
NoContext
1. `var NoContext =` 2. `/* */` 3. `0;` 4. `var BatchedContext =` 5. `/* */` 6. `1;` 7. `var EventContext =` 8. `/* */` 9. `2;` 10. `var DiscreteEventContext =` 11. `/* */` 12. `4;` 13. `var LegacyUnbatchedContext =` 14. `/* */` 15. `8;` 16. `var RenderContext =` 17. `/* */` 18. `16;` 19. `var CommitContext =` 20. `/* */` 21. `32;` 22. `var RetryAfterError =` 23. `/* */` 24. `64;`
分析位运算可能写出他们的二进制形式更加清晰一点,由上到下依次为:
1. `NoContext 0000000` 2. `BatchedContext 0000001` 3. `EventContext 0000010` 4. `DiscreteEventContext 0000100` 5. `LegacyUnbatchedContext 0001000` 6. `RenderContext 0010000` 7. `CommitContext 0100000` 8. `RetryAfterError 1000000`
实际参与计算时应该是 32 位的但是这里取 7 位是因为 2^6 是 64 多余的 0 对于我的分析来说是没有意义的。 react17 一共在 11 个地方对 context 进行了|=方式的更新,不同的更新方式对应了不同的 context 的变量,比方,batchedUpdates 的更新为executionContext |= BatchedContext;,unbatchedUpdates 中的更新则相应的为executionContext |= LegacyUnbatchedContext。
|=
executionContext |= BatchedContext;
executionContext |= LegacyUnbatchedContext
这些更新在其他地方大同小异,基本都是|=方式进行更新, 但在 unbatchedUpdates 更新前,却进行了一次这个操作:executionContext &= ~BatchedContext, 这里我们先不管这段代码具体是什么作用,我们暂且讨论一下这个二进制运算会产生何种效果。首先假设在某一状态时 executionContext 与 BatchedContext 发生了一次运算:
executionContext &= ~BatchedContext
1. `executionContext | BatchedContext` 2. `0000000 | 0000001` 3. `executionContext = 0000001`
那么 executionContext &= ~BatchedContext:
1. `executionContext & ~BatchedContext` 2. `0000001 & 1111110` 3. `executionContext = 0000000`
可以看到executionContext &= ~BatchedContext的效果其实就是还原了上次executionContext |= BatchedContext;前的 executionContext。这个其实很好理解,要知道所有的 context 他在以上的 7 位二进制中只占了其中的一位,那么无论之前的 executionContext 与其中那个 context 进行按位或|运算,其结果就是只是让 executionContext 的某个位为 1,拿 BatchedContext 举例他只是使得 executionContext 右边的第一位为 1,而在按位取反之后,除了自己所占的那个位为 0 其余都成了 1,再与 executionContext 进行按位与&运算,则executionContext中其他位为 1 的(也就是说 executionContext 可能与其他上下文也一起进行了|)是不会变得,只有~BatchedContext对应的那一位现在是 0,因此不管 executionContext 中这个位置的数字是 0 还是 1 其结果最后都为 0,也就是上边说的 executionContext 还原到了与 BatchedContext|=前的状态。这条准则换成其他任何一个 context 都是一样的。
|
&
~BatchedContext
拿一个地方举例:
1. `if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {` 2. `{` 3. `throw Error( "Should not already be working." );` 4. `}` 5. `}`
这个 if 里的表达式为真,即 左边的要不等于 NoContext,左边的不等于 NoContext 则说明 executionContext 里属于 RenderContext 或 CommitContext 的那个位为 1,而那个位为 1 就说明 executionContext 肯定与其中之一发生了按位或运算,而发生按位或也就代表着某个地方的代码执行过了。以上。
小结: 其实以上这种二进制的运算,应该属于二进制掩码的应用,二进制的运算除了数学上的特征比方左移右移相当于乘以 2 除以 2,其他方面更觉得像一种图形一样的运算,因为 & | 这两种运算是不会产生进位的, 因此看源码的二进制运算更多的应该从类似图形变换的角度去理解每个二进制运算的含义,而不是数字之间的运算。
关于 lane 有篇文章个人决得讲的特别好,推荐一看
react17 中使用了 31 位二进制来表示 lane 的概念,其中 31 位中占一位的变量称作 lane,占据多位的称为 lanes,react17 中全部 lane 如下 (二进制形式):
1. `export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;` 2. `export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;` 4. `export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;` 5. `export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;` 7. `export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;` 8. `const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;` 10. `const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;` 11. `const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;` 13. `export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;` 14. `export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;` 16. `const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;` 17. `const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;` 19. `const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;` 21. `export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;` 23. `export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;` 25. `const NonIdleLanes = /* */ 0b0000111111111111111111111111111;` 27. `export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;` 28. `const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;` 30. `export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;`
每一个 fiber 创建的时候其 lanes,childLanes 字段都被初始化为NoLanes
NoLanes
react 中无论是初始的渲染,还是 setstate 或者由 hooks 派发出来的更新操作,都会调用createupdate方法创建一个 update 对象,不同之处是,对于更新时的 update 对象来说 lane 字段是什么,是由与之相关的 fiber 的 mode 字段决定的:
createupdate
1. `...` 2. `var lane = requestUpdateLane(fiber);` 3. `var update = createUpdate(eventTime, lane);` 4. `...` 5. `function requestUpdateLane(fiber) {` 6. `...` 8. `var mode = fiber.mode;` 10. `if ((mode & BlockingMode) === NoMode) {` 11. `return SyncLane;` 12. `} else if ((mode & ConcurrentMode) === NoMode) {` 13. `return getCurrentPriorityLevel() === ImmediatePriority$1 ? SyncLane : SyncBatchedLane;` 14. `}` 15. `...` 16. `}`
mode 一般来说有如下几种:
1. `var NoMode = 0;` 2. `var StrictMode = 1;` 4. `var BlockingMode = 2;` 5. `var ConcurrentMode = 4;` 6. `var ProfileMode = 8;` 7. `var DebugTracingMode = 16;`
HostRootFiber(整个 react 应用的初始 fiber 节点)初始化的时候,目前来看其tag是LegacyRoot,在createHostRootFiber方法中赋予其 mode:
HostRootFiber(整个 react 应用的初始 fiber 节点)
tag
LegacyRoot
createHostRootFiber
1. `if (tag === ConcurrentRoot) {` 2. `mode = ConcurrentMode | BlockingMode | StrictMode;` 3. `} else if (tag === BlockingRoot) {` 4. `mode = BlockingMode | StrictMode;` 5. `} else {` 6. `mode = NoMode;` 7. `}`
由其 tag 可以知道 HostRootFiber 的 mode 即为 noMode,之后在 beginwork 开始创建各个子节点的 fiber 时,其 fiber 的 mode 直接继承自父节点:
1. `var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);`
因此 对于大部分 fiber 来说,在一次更新中由其派发的 update 的lane是 SyncLane。
lane
每个更新时都会自scheduleUpdateOnFiber始,而在scheduleUpdateOnFiber中,会
scheduleUpdateOnFiber
更新 fiber 上的 lanes 字段:
1. `sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);`
然后沿 fiber 树向上遍历,更新每个父节点 fiber 的 childLanes 字段
1. `while (parent !== null) {` 2. `...` 3. `parent.childLanes = mergeLanes(parent.childLanes, lane);` 4. `...` 5. `}`
其中 mergeLanes 就是将两个变量进行按位或运算,产生新的 lanes。 即由此可以看到,当前各种各样的更新的 lane 最终都会在根节点的 childLanes 字段上有体现。
更新 root 根节点的各个字段
pendingLanes:```
root.pendingLanes |= updateLane;
suspendedLanes,pingedLanes```
var higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
root.suspendedLanes &= higherPriorityLanes;
root.pingedLanes &= higherPriorityLanes;
几句解释: higherPriorityLanes - 1 ,比方代码中的注释`Turns 0b1000 into 0b0111`,他假如是一个lanes字段,那么他的值就是比当前updateLane的优先级更高的各个lane按位或之后的结果,因为结合前边各个lane的值可以看到,越靠近右边的1的位置的优先级越高, 至于suspendedLanes,pingedLanes的更新就是保留了比当前优先级更高的lane。
eventTimes 这是一个 31 长度的数组,每一位对应一个 lane```
var eventTimes = root.eventTimes;
var index = laneToIndex(updateLane); // 获取当前 lane 在 eventTimes 的数组索引
// We can always overwrite an existing timestamp because we prefer the most
/ recent event, and we assume time is monotonically increasing.
eventTimes[index] = eventTime;//eventTime 是创建当前 update 的时间
未完待续。。。 [https://www.php.cn/blog/detail/25899.html](https://www.php.cn/blog/detail/25899.html)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
关于 react 为什么会启用 lane 模型的官方解释
js 中的二进制位运算都是以 32 位补码的形式计算的,更多解释可以参考mdn
1. 关于上下文的切换
在 react 的更新中,
executionContext
按照字面意思即为执行上下文,executionContext 的默认值是 0NoContext
, 此后的 executionContext 的更新中,都是与其他不同的上下文以按位或的运算的方式进行更新的,react17 里的不同上下文有如下 8 种:分析位运算可能写出他们的二进制形式更加清晰一点,由上到下依次为:
实际参与计算时应该是 32 位的但是这里取 7 位是因为 2^6 是 64 多余的 0 对于我的分析来说是没有意义的。
react17 一共在 11 个地方对 context 进行了
|=
方式的更新,不同的更新方式对应了不同的 context 的变量,比方,batchedUpdates 的更新为executionContext |= BatchedContext;
,unbatchedUpdates 中的更新则相应的为executionContext |= LegacyUnbatchedContext
。这些更新在其他地方大同小异,基本都是
|=
方式进行更新, 但在 unbatchedUpdates 更新前,却进行了一次这个操作:executionContext &= ~BatchedContext
, 这里我们先不管这段代码具体是什么作用,我们暂且讨论一下这个二进制运算会产生何种效果。首先假设在某一状态时 executionContext 与 BatchedContext 发生了一次运算:那么
executionContext &= ~BatchedContext
:可以看到
executionContext &= ~BatchedContext
的效果其实就是还原了上次executionContext |= BatchedContext;
前的 executionContext。这个其实很好理解,要知道所有的 context 他在以上的 7 位二进制中只占了其中的一位,那么无论之前的 executionContext 与其中那个 context 进行按位或|
运算,其结果就是只是让 executionContext 的某个位为 1,拿 BatchedContext 举例他只是使得 executionContext 右边的第一位为 1,而在按位取反之后,除了自己所占的那个位为 0 其余都成了 1,再与 executionContext 进行按位与&
运算,则executionContext
中其他位为 1 的(也就是说 executionContext 可能与其他上下文也一起进行了|
)是不会变得,只有~BatchedContext
对应的那一位现在是 0,因此不管 executionContext 中这个位置的数字是 0 还是 1 其结果最后都为 0,也就是上边说的 executionContext 还原到了与 BatchedContext|=
前的状态。这条准则换成其他任何一个 context 都是一样的。2. 关于上下文的判断
拿一个地方举例:
这个 if 里的表达式为真,即 左边的要不等于 NoContext,左边的不等于 NoContext 则说明 executionContext 里属于 RenderContext 或 CommitContext 的那个位为 1,而那个位为 1 就说明 executionContext 肯定与其中之一发生了按位或运算,而发生按位或也就代表着某个地方的代码执行过了。以上。
3. 关于 lane
关于 lane 有篇文章个人决得讲的特别好,推荐一看
关于 lane 的基本解释
react17 中使用了 31 位二进制来表示 lane 的概念,其中 31 位中占一位的变量称作 lane,占据多位的称为 lanes,react17 中全部 lane 如下 (二进制形式):
关于 lane 的基本使用
1. 创建 fiber
每一个 fiber 创建的时候其 lanes,childLanes 字段都被初始化为
NoLanes
2. 创建 update
react 中无论是初始的渲染,还是 setstate 或者由 hooks 派发出来的更新操作,都会调用
createupdate
方法创建一个 update 对象,不同之处是,对于更新时的 update 对象来说 lane 字段是什么,是由与之相关的 fiber 的 mode 字段决定的:mode 一般来说有如下几种:
HostRootFiber(整个 react 应用的初始 fiber 节点)
初始化的时候,目前来看其tag
是LegacyRoot
,在createHostRootFiber
方法中赋予其 mode:由其 tag 可以知道 HostRootFiber 的 mode 即为 noMode,之后在 beginwork 开始创建各个子节点的 fiber 时,其 fiber 的 mode 直接继承自父节点:
因此 对于大部分 fiber 来说,在一次更新中由其派发的 update 的
lane
是 SyncLane。3. 更新过程中对 fiber 上各个字段的更新
每个更新时都会自
scheduleUpdateOnFiber
始,而在scheduleUpdateOnFiber
中,会更新 fiber 上的 lanes 字段:
然后沿 fiber 树向上遍历,更新每个父节点 fiber 的 childLanes 字段
其中 mergeLanes 就是将两个变量进行按位或运算,产生新的 lanes。 即由此可以看到,当前各种各样的更新的 lane 最终都会在根节点的 childLanes 字段上有体现。
更新 root 根节点的各个字段
pendingLanes:```
root.pendingLanes |= updateLane;
suspendedLanes,pingedLanes```
var higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
root.suspendedLanes &= higherPriorityLanes;
root.pingedLanes &= higherPriorityLanes;
eventTimes
这是一个 31 长度的数组,每一位对应一个 lane```
var eventTimes = root.eventTimes;
var index = laneToIndex(updateLane); // 获取当前 lane 在 eventTimes 的数组索引
// We can always overwrite an existing timestamp because we prefer the most
/ recent event, and we assume time is monotonically increasing.
eventTimes[index] = eventTime;//eventTime 是创建当前 update 的时间
The text was updated successfully, but these errors were encountered: