• hooks 内部结构


    hooks 内部结构

    最近在对 React 进行一些深入化的学习,把困扰很久的疑惑 查资料弄清楚啦!

    疑惑

    const [age, setAge] = useState(initialAge);
    const [name, setName] = useState(initialName);
    
    • 1
    • 2

    1. 当使用这两个 状态的时候,hooks 是怎么区分这两个状态的呢?
    2. hooks是如何在每次重新渲染之后都能返回最新的状态?

    React内部是怎么区分这两个状态的呢? Function Component 不像 Class Component那样可以将私有状态挂载到类实例中并通过对应的key来指向对应的状态,而且每次的页面的刷新或者说组件的重新渲染都会使得 Function 重新执行一遍。所以React中必定有一种机制来区分这些Hooks。

    React是如何在每次重新渲染之后都能返回最新的状态? Class Component因为自身的特点可以将私有状态持久化的挂载到类实例上,每时每刻保存的都是最新的值。而 Function Component 由于本质就是一个函数,并且每次渲染都会重新执行。所以React必定拥有某种机制去记住每一次的更新操作,并最终得出最新的值返回。比如这些状态究竟存放在哪?为什么只能在函数顶层使用Hooks而不能在条件语句等里面使用Hooks?

    答案

    // react-reconciler/src/ReactFiberHooks.js
    export type Hook = {
      memoizedState: any,
      baseState: any,
      baseUpdate: Update | null,
      queue: UpdateQueue | null,
      next: Hook | null,  // 指向下一个Hook
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    从Hook的类型定义中就可以看到,React 对Hooks的定义是链表。也就是说我们组件里使用到的Hooks是通过链表来联系的,上一个Hooks的next指向下一个Hooks。

    在mount阶段,就会生产如下图这样的单链表:
    在这里插入图片描述

    如何返回最新的值?
    useState和useReducer都是使用了一个queue链表来存放每一次的更新。以便后面的update阶段可以返回最新的状态。每次我们调用dispatchAction方法的时候,就会形成一个新的updata对象,添加到queue链表上,而且这个是一个循环链表。
    可以看一下 dispatchAction 方法的实现:

    // react-reconciler/src/ReactFiberHooks.js
    // 去除特殊情况和与fiber相关的逻辑
    function dispatchAction(fiber,queue,action,) {
        const update = {
          action,
          next: null,
        };
        // 将update对象添加到循环链表中
        const last = queue.last;
        if (last === null) {
          // 链表为空,将当前更新作为第一个,并保持循环
          update.next = update;
        } else {
          const first = last.next;
          if (first !== null) {
            // 在最新的update对象后面插入新的update对象
            update.next = first;
          }
          last.next = update;
        }
        // 将表头保持在最新的update对象上
        queue.last = update;
       // 进行调度工作
        scheduleWork();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    也就是我们每次执行dispatchAction方法,比如setAge或setName。就会创建一个保存着此次更新信息的update对象,添加到更新链表queue上。然后每个Hooks节点就会有自己的一个queque。比如假设我们执行了下面几个语句:
    setAge(19);
    setAge(20);
    setAge(21);

    那么我们的Hooks链表就会变成这样:
    在这里插入图片描述

    在Hooks节点上面,会如上图那样,通过链表来存放所有的历史更新操作。以便在update阶段可以通过这些更新获取到最新的值返回给我们。这就是在第一次调用useState或useReducer之后,每次更新都能返回最新值的原因。

    在update阶段,也就是我们组件第二次第三次执行到useState或useReducer的时候,会遍历update对象循环链表,执行每一次更新去计算出最新的状态来返回,以保证我们每次刷新组件都能拿到当前最新的状态。

    在这里插入图片描述

    总结

    看到这里我们在回头看看最初的一些疑问:

    1. React 如何管理区分Hooks?

    React通过单链表来管理Hooks
    按Hooks的执行顺序依次将Hook节点添加到链表中

    1. useState和useReducer如何在每次渲染时,返回最新的值?

    每个Hook节点通过循环链表记住所有的更新操作
    在update阶段会依次执行update循环链表中的所有更新操作,最终拿到最新的state返回

    1. 为什么不能在条件语句等中使用Hooks?

    在这里插入图片描述

    比如如图所示,我们在mount阶段调用了useState(‘A’), useState(‘B’), useState(‘C’),如果我们将useState(‘B’) 放在条件语句内执行,并且在update阶段中因为不满足条件而没有执行的话,那么没法正确的重Hooks链表中获取信息。React也会给我们报错。

    Hooks链表放在哪?

    组件构建的Hooks链表会挂载到FiberNode节点的memoizedState上面去。
    在这里插入图片描述

    参考文章:
    这一篇写的特别好
    https://juejin.cn/post/6844904080758800392

  • 相关阅读:
    k3s 指南
    mongodb手动迁移chunk脚本
    常用排序算法
    详解GaussDB(DWS) 资源监控
    智慧燃气巡检管理系统解决燃气管理问题
    gstreamer registry文件
    Ladybug 全景相机, 360°球形成像,带来全方位的视觉体验
    RabbitMQ保姆级教程最佳实践
    方舟开服务器怎么开
    C语言-【指针二】-【指针运算/指针和数组】
  • 原文地址:https://blog.csdn.net/qq_45803050/article/details/127622946