Skip to content
New issue

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源码中部分二进制计算的解释 - something about react - php中文网博客 #27

Open
zepang opened this issue Jan 10, 2022 · 0 comments

Comments

@zepang
Copy link
Owner

zepang commented Jan 10, 2022

react17 放弃了之前的 expirationTime 而启用了 lane 模型,故而在原来 16 的基础上又产生了更多的二进制运算,在接下来的一段时间我打算把这些二进制运算都整明白了、

关于 react 为什么会启用 lane 模型的官方解释
js 中的二进制位运算都是以 32 位补码的形式计算的,更多解释可以参考mdn

1. 关于上下文的切换

在 react 的更新中,executionContext按照字面意思即为执行上下文,executionContext 的默认值是 0NoContext, 此后的 executionContext 的更新中,都是与其他不同的上下文以按位或的运算的方式进行更新的,react17 里的不同上下文有如下 8 种:



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

这些更新在其他地方大同小异,基本都是|=方式进行更新, 但在 unbatchedUpdates 更新前,却进行了一次这个操作: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 都是一样的。

2. 关于上下文的判断

拿一个地方举例:



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,其他方面更觉得像一种图形一样的运算,因为 & | 这两种运算是不会产生进位的, 因此看源码的二进制运算更多的应该从类似图形变换的角度去理解每个二进制运算的含义,而不是数字之间的运算。

3. 关于 lane

关于 lane 有篇文章个人决得讲的特别好,推荐一看

关于 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;`


关于 lane 的基本使用
1. 创建 fiber

每一个 fiber 创建的时候其 lanes,childLanes 字段都被初始化为NoLanes

2. 创建 update

react 中无论是初始的渲染,还是 setstate 或者由 hooks 派发出来的更新操作,都会调用createupdate方法创建一个 update 对象,不同之处是,对于更新时的 update 对象来说 lane 字段是什么,是由与之相关的 fiber 的 mode 字段决定的:



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 节点)初始化的时候,目前来看其tagLegacyRoot,在createHostRootFiber方法中赋予其 mode:



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。

3. 更新过程中对 fiber 上各个字段的更新

每个更新时都会自scheduleUpdateOnFiber始,而在scheduleUpdateOnFiber中,会

  1. 更新 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 字段上有体现。

  2. 更新 root 根节点的各个字段

  • pendingLanes:```

    1. root.pendingLanes |= updateLane;
    
    
  • suspendedLanes,pingedLanes```

    1. var higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
    2. root.suspendedLanes &= higherPriorityLanes;
    3. root.pingedLanes &= higherPriorityLanes;
    
    几句解释: higherPriorityLanes - 1 ,比方代码中的注释`Turns 0b1000 into 0b0111`,他假如是一个lanes字段,那么他的值就是比当前updateLane的优先级更高的各个lane按位或之后的结果,因为结合前边各个lane的值可以看到,越靠近右边的1的位置的优先级越高, 至于suspendedLanes,pingedLanes的更新就是保留了比当前优先级更高的lane。
    
  • eventTimes
    这是一个 31 长度的数组,每一位对应一个 lane```

    1. var eventTimes = root.eventTimes;
    2. var index = laneToIndex(updateLane); // 获取当前 lane 在 eventTimes 的数组索引
    3. // We can always overwrite an existing timestamp because we prefer the most
    4. / recent event, and we assume time is monotonically increasing.
    5. eventTimes[index] = eventTime;//eventTime 是创建当前 update 的时间
      
      未完待续。。。 
      [https://www.php.cn/blog/detail/25899.html](https://www.php.cn/blog/detail/25899.html)
      
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant