码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【React源码】(十三)Hook 原理(状态 Hook)


    Hook 原理(状态 Hook)

    首先回顾一下前文Hook 原理(概览), 其主要内容有:

    1. function类型的fiber节点, 它的处理函数是updateFunctionComponent, 其中再通过renderWithHooks调用function.
    2. 在function中, 通过Hook Api(如: useState, useEffect)创建Hook对象.
      • 状态Hook实现了状态持久化(等同于class组件维护fiber.memoizedState).
      • 副作用Hook则实现了维护fiber.flags,并提供副作用回调(类似于class组件的生命周期回调)
    3. 多个Hook对象构成一个链表结构, 并挂载到fiber.memoizedState之上.
    4. fiber树更新阶段, 把current.memoizedState链表上的所有Hook按照顺序克隆到workInProgress.memoizedState上, 实现数据的持久化.

    在此基础之上, 本节将深入分析状态Hook的特性和实现原理.

    创建 Hook

    在fiber初次构造阶段, useState对应源码mountState, useReducer对应源码mountReducer

    mountState:

     
    
    1. function mountState<S>(
    2. initialState: (() => S) | S,
    3. ): [S, Dispatch<BasicStateAction<S>>] {
    4. // 1. 创建hook
    5. const hook = mountWorkInProgressHook();
    6. if (typeof initialState === 'function') {
    7. initialState = initialState();
    8. }
    9. // 2. 初始化hook的属性
    10. // 2.1 设置 hook.memoizedState/hook.baseState
    11. // 2.2 设置 hook.queue
    12. hook.memoizedState = hook.baseState = initialState;
    13. const queue = (hook.queue = {
    14. pending: null,
    15. dispatch: null,
    16. // queue.lastRenderedReducer是内置函数
    17. lastRenderedReducer: basicStateReducer,
    18. lastRenderedState: (initialState: any),
    19. });
    20. // 2.3 设置 hook.dispatch
    21. const dispatch: Dispatch<
    22. BasicStateAction<S>,
    23. > = (queue.dispatch = (dispatchAction.bind(
    24. null,
    25. currentlyRenderingFiber,
    26. queue,
    27. ): any));
    28. // 3. 返回[当前状态, dispatch函数]
    29. return [hook.memoizedState, dispatch];
    30. }
    31. mountReducer:
    32. function mountReducer<S, I, A>(
    33. reducer: (S, A) => S,
    34. initialArg: I,
    35. init?: I => S,
    36. ): [S, Dispatch<A>] {
    37. // 1. 创建hook
    38. const hook = mountWorkInProgressHook();
    39. let initialState;
    40. if (init !== undefined) {
    41. initialState = init(initialArg);
    42. } else {
    43. initialState = ((initialArg: any): S);
    44. }
    45. // 2. 初始化hook的属性
    46. // 2.1 设置 hook.memoizedState/hook.baseState
    47. hook.memoizedState = hook.baseState = initialState;
    48. // 2.2 设置 hook.queue
    49. const queue = (hook.queue = {
    50. pending: null,
    51. dispatch: null,
    52. // queue.lastRenderedReducer是由外传入
    53. lastRenderedReducer: reducer,
    54. lastRenderedState: (initialState: any),
    55. });
    56. // 2.3 设置 hook.dispatch
    57. const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    58. null,
    59. currentlyRenderingFiber,
    60. queue,
    61. ): any));
    62. // 3. 返回[当前状态, dispatch函数]
    63. return [hook.memoizedState, dispatch];
    64. }

    mountState和mountReducer逻辑简单: 主要负责创建hook, 初始化hook的属性, 最后返回[当前状态, dispatch函数].

    唯一的不同点是hook.queue.lastRenderedReducer:

    • mountState使用的是内置的basicStateReducer
       
      1. function basicStateReducer(state: S, action: BasicStateAction): S {
      2. return typeof action === 'function' ? action(state) : action;
      3. }

    • mountReducer使用的是外部传入自定义reducer

    可见mountState是mountReducer的一种特殊情况, 即useState也是useReducer的一种特殊情况, 也是最简单的情况.

    useState可以转换成useReducer:

     
    
    1. const [state, dispatch] = useState({ count: 0 });
    2. // 等价于
    3. const [state, dispatch] = useReducer(
    4. function basicStateReducer(state, action) {
    5. return typeof action === 'function' ? action(state) : action;
    6. },
    7. { count: 0 },
    8. );
    9. // 当需要更新state时, 有2种方式
    10. dispatch({ count: 1 }); // 1.直接设置
    11. dispatch(state => ({ count: state.count + 1 })); // 2.通过回调函数设置

    userReducer的官网示例:

     
    
    1. const [state, dispatch] = useReducer(
    2. function reducer(state, action) {
    3. switch (action.type) {
    4. case 'increment':
    5. return { count: state.count + 1 };
    6. case 'decrement':
    7. return { count: state.count - 1 };
    8. default:
    9. throw new Error();
    10. }
    11. },
    12. { count: 0 },
    13. );// 当需要更新state时, 只有1种方式

    dispatch({ type: 'decrement' });

    可见, useState就是对useReducer的基本封装, 内置了一个特殊的reducer(后文不再区分useState, useReducer, 都以useState为例).创建hook之后返回值[hook.memoizedState, dispatch]中的dispatch实际上会调用reducer函数.

    状态初始化

    在useState(initialState)函数内部, 设置hook.memoizedState = hook.baseState = initialState;, 初始状态被同时保存到了hook.baseState,hook.memoizedState中.

    1. hook.memoizedState: 当前状态
    2. hook.baseState: 基础状态, 作为合并hook.baseQueue的初始值(下文介绍).

    最后返回[hook.memoizedState, dispatch], 所以在function中使用的是hook.memoizedState.

    状态更新

    有如下代码:hook-status - CodeSandbox

    0

    初次渲染时count = 0, 这时hook对象的内存状态如下:

    点击button, 通过dispatch函数进行更新, dispatch实际就是dispatchAction:

     
    
    1. function dispatchAction(
    2. fiber: Fiber,
    3. queue: UpdateQueue,
    4. action: A,
    5. ) {
    6. // 1. 创建update对象
    7. const eventTime = requestEventTime();
    8. const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
    9. const update: Update = {
    10. lane,
    11. action,
    12. eagerReducer: null,
    13. eagerState: null,
    14. next: (null: any),
    15. };
    16. // 2. 将update对象添加到hook.queue.pending队列
    17. const pending = queue.pending;
    18. if (pending === null) {
    19. // 首个update, 创建一个环形链表
    20. update.next = update;
    21. } else {
    22. update.next = pending.next;
    23. pending.next = update;
    24. }
    25. queue.pending = update;
    26. const alternate = fiber.alternate;
    27. if (
    28. fiber === currentlyRenderingFiber ||
    29. (alternate !== null && alternate === currentlyRenderingFiber)
    30. ) {
    31. // 渲染时更新, 做好全局标记
    32. didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    33. } else {
    34. // ...省略性能优化部分, 下文介绍
    35. // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
    36. scheduleUpdateOnFiber(fiber, lane, eventTime);
    37. }
    38. }

    逻辑十分清晰:

    1. 创建update对象, 其中update.lane代表优先级(可回顾fiber 树构造(基础准备)中的update优先级).
    2. 将update对象添加到hook.queue.pending环形链表.
      • 环形链表的特征: 为了方便添加新元素和快速拿到队首元素(都是O(1)), 所以pending指针指向了链表中最后一个元素.
      • 链表的使用方式可以参考React 算法之链表操作
    3. 发起调度更新: 调用scheduleUpdateOnFiber, 进入reconciler 运作流程中的输入阶段.

    从调用scheduleUpdateOnFiber开始, 进入了react-reconciler包, 其中的所有逻辑可回顾reconciler 运作流程, 本节只讨论状态Hook相关逻辑.

    注意: 本示例中虽然同时执行了 3 次 dispatch, 会请求 3 次调度, 由于调度中心的节流优化, 最后只会执行一次渲染

    在fiber树构造(对比更新)过程中, 再次调用function, 这时useState对应的函数是updateState

     
    
    1. function updateState(
    2. initialState: (() => S) | S,
    3. ): [S, Dispatch<BasicStateAction>] {
    4. return updateReducer(basicStateReducer, (initialState: any));
    5. }

    实际调用updateReducer.

    在执行updateReducer之前, hook相关的内存结构如下:

     
    
    1. function updateReducer(
    2. reducer: (S, A) => S,
    3. initialArg: I,
    4. init?: I => S,
    5. ): [S, Dispatch] {
    6. // 1. 获取workInProgressHook对象
    7. const hook = updateWorkInProgressHook();
    8. const queue = hook.queue;
    9. queue.lastRenderedReducer = reducer;
    10. const current: Hook = (currentHook: any);
    11. let baseQueue = current.baseQueue;
    12. // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
    13. const pendingQueue = queue.pending;
    14. if (pendingQueue !== null) {
    15. if (baseQueue !== null) {
    16. const baseFirst = baseQueue.next;
    17. const pendingFirst = pendingQueue.next;
    18. baseQueue.next = pendingFirst;
    19. pendingQueue.next = baseFirst;
    20. }
    21. current.baseQueue = baseQueue = pendingQueue;
    22. queue.pending = null;
    23. }
    24. // 3. 状态计算
    25. if (baseQueue !== null) {
    26. const first = baseQueue.next;
    27. let newState = current.baseState;
    28. let newBaseState = null;
    29. let newBaseQueueFirst = null;
    30. let newBaseQueueLast = null;
    31. let update = first;
    32. do {
    33. const updateLane = update.lane;
    34. // 3.1 优先级提取update
    35. if (!isSubsetOfLanes(renderLanes, updateLane)) {
    36. // 优先级不够: 加入到baseQueue中, 等待下一次render
    37. const clone: Update = {
    38. lane: updateLane,
    39. action: update.action,
    40. eagerReducer: update.eagerReducer,
    41. eagerState: update.eagerState,
    42. next: (null: any),
    43. };
    44. if (newBaseQueueLast === null) {
    45. newBaseQueueFirst = newBaseQueueLast = clone;
    46. newBaseState = newState;
    47. } else {
    48. newBaseQueueLast = newBaseQueueLast.next = clone;
    49. }
    50. currentlyRenderingFiber.lanes = mergeLanes(
    51. currentlyRenderingFiber.lanes,
    52. updateLane,
    53. );
    54. markSkippedUpdateLanes(updateLane);
    55. } else {
    56. // 优先级足够: 状态合并
    57. if (newBaseQueueLast !== null) {
    58. // 更新baseQueue
    59. const clone: Update = {
    60. lane: NoLane,
    61. action: update.action,
    62. eagerReducer: update.eagerReducer,
    63. eagerState: update.eagerState,
    64. next: (null: any),
    65. };
    66. newBaseQueueLast = newBaseQueueLast.next = clone;
    67. }
    68. if (update.eagerReducer === reducer) {
    69. // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer
    70. newState = ((update.eagerState: any): S);
    71. } else {
    72. const action = update.action;
    73. // 调用reducer获取最新状态
    74. newState = reducer(newState, action);
    75. }
    76. }
    77. update = update.next;
    78. } while (update !== null && update !== first);
    79. // 3.2. 更新属性
    80. if (newBaseQueueLast === null) {
    81. newBaseState = newState;
    82. } else {
    83. newBaseQueueLast.next = (newBaseQueueFirst: any);
    84. }
    85. if (!is(newState, hook.memoizedState)) {
    86. markWorkInProgressReceivedUpdate();
    87. }
    88. // 把计算之后的结果更新到workInProgressHook上
    89. hook.memoizedState = newState;
    90. hook.baseState = newBaseState;
    91. hook.baseQueue = newBaseQueueLast;
    92. queue.lastRenderedState = newState;
    93. }
    94. const dispatch: Dispatch = (queue.dispatch: any);
    95. return [hook.memoizedState, dispatch];
    96. }

    updateReducer函数, 代码相对较长, 但是逻辑分明:

    1. 调用updateWorkInProgressHook获取workInProgressHook对象

    2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue

    3. 状态计算

      1. update优先级不够: 加入到 baseQueue 中, 等待下一次 render

      2. update优先级足够: 状态合并

      3. 更新属性

    性能优化

    dispatchAction函数中, 在调用scheduleUpdateOnFiber之前, 针对update对象做了性能优化.

    1. queue.pending中只包含当前update时, 即当前update是queue.pending中的第一个update
    2. 直接调用queue.lastRenderedReducer,计算出update之后的 state, 记为eagerState
    3. 如果eagerState与currentState相同, 则直接退出, 不用发起调度更新.
    4. 已经被挂载到queue.pending上的update会在下一次render时再次合并.
     
    
    1. function dispatchAction(
    2. fiber: Fiber,
    3. queue: UpdateQueue,
    4. action: A,
    5. ) {
    6. // ...省略无关代码 ...只保留性能优化部分代码:
    7. // 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动(可以回顾`fiber 树构造(对比更新)`章节), 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update
    8. if (
    9. fiber.lanes === NoLanes &&
    10. (alternate === null || alternate.lanes === NoLanes)
    11. ) {
    12. const lastRenderedReducer = queue.lastRenderedReducer;
    13. if (lastRenderedReducer !== null) {
    14. let prevDispatcher;
    15. const currentState: S = (queue.lastRenderedState: any);
    16. const eagerState = lastRenderedReducer(currentState, action);
    17. // 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算
    18. update.eagerReducer = lastRenderedReducer;
    19. update.eagerState = eagerState;
    20. if (is(eagerState, currentState)) {
    21. // 快速通道, eagerState与currentState相同, 无需调度更新
    22. // 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用
    23. return;
    24. }
    25. }
    26. }
    27. // 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
    28. scheduleUpdateOnFiber(fiber, lane, eventTime);
    29. }

    为了验证上述优化, 可以查看这个 demo:hook-throttle - CodeSandbox

    异步更新

    上述示例都是为在Legacy模式下, 所以均为同步更新. 所以update对象会被全量合并,hook.baseQueue和hook.baseState并没有起到实质作用.

    虽然在v17.x版本中, 并没有Concurrent模式的入口, 即将发布的v18.x版本将全面进入异步时代, 所以本节提前梳理一下update异步合并的逻辑. 同时加深hook.baseQueue和hook.baseState的理解.

    假设有一个queue.pending链表, 其中update优先级不同, 绿色表示高优先级, 灰色表示低优先级, 红色表示最高优先级.

    在执行updateReducer之前, hook.memoizedState有如下结构(其中update3, update4是低优先级):

    链表拼接:

    • 和同步更新时一致, 直接把queue.pending拼接到current.baseQueue

    状态计算:

    • 只会提取update1, update2这 2 个高优先级的update, 所以最后memoizedState=2
    • 保留其余低优先级的update, 等待下一次render
    • 从第一个低优先级update3开始, 随后的所有update都会被添加到baseQueue, 由于update2已经是高优先级, 会设置update2.lane=NoLane将优先级升级到最高(红色表示).
    • 而baseState代表第一个低优先级update3之前的state, 在本例中, baseState=1

    function节点被处理完后, 高优先级的update, 会率先被使用(memoizedState=2). 一段时间后, 低优先级update3, update4符合渲染, 这种情况下再次执行updateReducer重复之前的步骤.

    链表拼接:

    • 由于queue.pending = null, 故拼接前后没有实质变化

    状态计算:

    • 现在所有update.lane都符合渲染优先级, 所以最后的内存结构与同步更新一致(memoizedState=4,baseState=4).

    结论: 尽管update链表的优先级不同, 中间的render可能有多次, 但最终的更新结果等于update链表按顺序合并.

    总结

    本节深入分析状态Hook即useState的内部原理, 从同步,异步更新理解了update对象的合并方式, 最终结果存储在hook.memoizedState供给function使用.

  • 相关阅读:
    商业楼智能照明解决方案,配套产品有哪些
    使用WebView控件进行网络开发
    java blob 转文件
    【Ruby】怎样判断一个类是否有某个方法,一个实例是否有某个属性?
    Qt跨平台(统信UOS)各种坑解决办法
    算法:JavaScript语言描述
    【OpenCV】图形绘制与填充
    一看即懂的JavaScript位运算
    第十四届蓝桥杯第一期模拟赛试题与题解 C++
    交通物流模型 | MDRGCN:用于多模式交通客流预测的深度学习模型
  • 原文地址:https://blog.csdn.net/weixin_44828588/article/details/126525632
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号