useState源码解读 及 手撕 useState 实现

news2024/12/24 2:34:19

文章目录

  • useState源码解读 及 手撕 useState 实现
    • useState源码分析
      • 逻辑图
      • 源码解读
        • mountState
          • mountWorkInProgressHook 函数
        • updateState
          • updateReducer 函数
    • 实现
    • 对比图
    • 实现效果
      • 只声明一个 hook
      • 重复调用同一个 hook
      • 声明多个不同的 hooks
    • 体验收获

useState源码解读 及 手撕 useState 实现

useState源码分析

逻辑图

在这里插入图片描述

源码解读

hooks 保存在 packages/react-reconciler/src/ReactFiberHooks.old.js 文件中

有一个 dispatcher 的数据结构,是个对象,在不同的 dispatcher 中,都同样存在了 useState;

在不同的 dispatcher 中,useState 的实现对应的是不同的方法:

即 useState 在不同的上下文中对应的是不同的函数
所以:react 通过在不同的上下文使用不同的 dispatcher,来区分当前需要使用 hooks 的不同实现

比如:
在这里插入图片描述

mountState

首先看 mountState 的实现:

  1. 调用 mountWorkInProgressHook 创建hook对象
  2. 初始化 memoizedState 和 baseState ,值为 initialState
  3. 创建 hook 的 updateQueue
  4. 创建 dispatch 方法(其实就是 绑定了当前的 fiber和 queue 的 dispatchAction )

在这里插入图片描述

mountWorkInProgressHook 函数
  1. 创建一个 hook 对象
  2. 若这是第一个 hook,挂载到 memoizedState
  3. 若不是第一个hook的话,就会把他挂载到上一个 hook的 next 指针下,与上一个hook形成一条链表
  4. 返回 该hook 对象

在这里插入图片描述

updateState

在这里插入图片描述

其实,useState 就是一个预置了 reducer 的 useReducer,预置的 reducer 就是 basicStateReducer

在这里插入图片描述

updateReducer 函数

总结:首先获取当前 hooks和 当前的 queue,之后就会 根据 baseState 和 拥有优先级 的 update来计算 memoizedState

具体代码在这:

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  invariant(
    queue !== null,
    'Should have a queue. This is likely a bug in React. Please file an issue.',
  );

  queue.lastRenderedReducer = reducer;

  const current: Hook = (currentHook: any);

  // The last rebase update that is NOT part of the base state.
  let baseQueue = current.baseQueue;

  // The last pending update that hasn't been processed yet.
  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    // We have new updates that haven't been processed yet.
    // We'll add them to the base queue.
    if (baseQueue !== null) {
      // Merge the pending queue and the base queue.
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    if (__DEV__) {
      if (current.baseQueue !== baseQueue) {
        // Internal invariant that should never happen, but feasibly could in
        // the future if we implement resuming, or some form of that.
        console.error(
          'Internal error: Expected work-in-progress queue to be a clone. ' +
            'This is a bug in React.',
        );
      }
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }

  if (baseQueue !== null) {
    // We have a queue to process.
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          eagerReducer: update.eagerReducer,
          eagerState: update.eagerState,
          next: (null: any),
        };
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // Update the remaining priority in the queue.
        // TODO: Don't need to accumulate this. Instead, we can remove
        // renderLanes from the original lanes.
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        // This update does have sufficient priority.

        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            // This update is going to be committed so we never want uncommit
            // it. Using NoLane works because 0 is a subset of all bitmasks, so
            // this will never be skipped by the check above.
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        // Process this update.
        if (update.eagerReducer === reducer) {
          // If this update was processed eagerly, and its reducer matches the
          // current reducer, we can use the eagerly computed state.
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

查阅各种文档资料视频等,勉勉强强的弄懂了 useState 的实现原理,今天来简单实现一下 useState,思路都在 代码注释中:

实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>

        // 在 react 中,会通过判断 currentFiber 是否存在,来区别是 mount 还是 update
        // 在这个简易版本中,通过一个 全局变量来区分
        let isMount = true;

        // workInProgressHook 指向当前正在执行的 hooks
        let workInProgressHook = null;


        // 模拟 schedule,render,commit 这个流程
        function run() {
            //hooks 的初始化操作,初始化为第一个 hook
            workInProgressHook = fiber.memorizedState;

            // 模拟 render 阶段,render 阶段会触发 App 函数
            const app = fiber.stateNode();
            isMount = false;
            return app;
        }

        // 作用:创建 update,并将 update 连成一条环状链表
        // 这样我们在调用 useState 中,才能从 hook.queue.pending 中取到这条环状链表
        function dispatchAction(queue, action) {
            const update = {
                action,
                next: null
            };

            if (queue.pending === null) { // 还不存在 update
                update.next = update; // 第一个 update 会与自己形成 环状链表
            } else {
                // 环形链表操作
                // 例子: 3 -> 0 -> 1 -> 2 -> 3
                // 转变为 4 -> 0 -> 1 -> 2 -> 3 -> 4    

                // update 代表当前的 update,queue.pending.next 代表 第一个,
                // 即实现 从 3 -> 0 变为 4 -> 0 
                update.next = queue.pending.next;

                // 实现 从 -> 3 变为 3 -> 4   
                queue.pending.next = update;


            }

            queue.pending = update; // queue.pending 指向的是最后一个 update 

            run();
        }

        // 函数组件有一个对应的 fiber
        const fiber = {
            /**
             * memorizedState 属性用于保存 hooks
             * 是使用的 链表的结构来保存的hooks
            */
            memorizedState: null,
            stateNode: App // stateNode 保存了对应的 function
        }

        // 实现 useState
        function useState(initialState) {
            let hook;

            if (isMount) { // mount 阶段
                // 创建 hooks 链表(和 update queue 是类似的)
                hook = {
                    queue: {
                        pending: null
                    },
                    memorizedState: initialState, // 保存了 hooks 对应的state 的属性
                    next: null  // next 指向下一个 hook
                }

                if (!fiber.memorizedState) {
                    fiber.memorizedState = hook; // hooks 初始化
                } else {
                    workInProgressHook.next = hook;
                }

                workInProgressHook = hook; // 以上实现了将多个hook连接成一个单向链表


            } else { // update 阶段
                hook = workInProgressHook;
                workInProgressHook = workInProgressHook.next;
            }

            // 此时 hook 变量就是当前的 hook 对象
            // 注意:这个版本中省略了 state 优先级的考虑,所以只需要实现 state 中的 baseState
            //      即  baseState 就是 memorizedState

            // 计算 state
            let baseState = hook.memorizedState;
            if (hook.queue.pending) { // 此时的hook有需要计算的 update
                /**
                 * hooks 在 update 阶段中,在 updateQueue 中是以环状链表保存的
                 * 
                 * hook.queue.pending 保存了最后一个 update
                 * 所以 hook.queue.pending.next 就指向了第一个 update
                */
                let firstUpdate = hook.queue.pending.next;

                // 遍历链表
                do {
                    const action = firstUpdate.action; // action 表示 updateNum 传入的参数值(这里是函数 num => num + 1)
                    baseState = action(baseState);
                    firstUpdate = firstUpdate.next;

                } while (firstUpdate !== hook.queue.pending.next);

                hook.queue.pending = null; // update 计算完毕
            }


            // 进行更新
            hook.memorizedState = baseState;

            // 函数组件改变 update 的这个函数叫 dispatchAction
            return [baseState, dispatchAction.bind(null, hook.queue)]

        }


        function App() {
            const [num, updateNum] = useState(0);
            const [flag, updateFlag] = useState(false);

            console.log('isMount:', isMount);
            console.log('num', num);
            console.log('flag:', flag);

            return {
                onClick() {
                    updateNum(num => num + 1);
                },
                change() {
                    updateFlag(flag => !flag);
                }
            }

        }


        window.app = run();

    </script>
</body>

</html>

对比图

在这里插入图片描述

在这里插入图片描述

实现效果

只声明一个 hook

在这里插入图片描述

在这里插入图片描述

重复调用同一个 hook

在这里插入图片描述

在这里插入图片描述

声明多个不同的 hooks

在这里插入图片描述

在这里插入图片描述

体验收获

源码确实很难,需要花很多时间,但是弄懂了之后,很有成就感,也觉得 react 没有那么神秘了,加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/64087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL分区表对NULL值的处理

GreatSQL社区原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本&#xff0c;使用上与MySQL一致。作者&#xff1a;王权富贵 1.概述 MySQL的分区表没有禁止NULL值作为分区表达式的值&#xff0c;无论它是列值还是用户提供的表…

CPP 核心编程4-重载递增运算符

#include "iostream"using namespace std;//递增运算符重载 //自定义整型 class MyInteger {friend ostream &operator<<(ostream &cout, MyInteger mi);public:MyInteger() {m_Num 0;}//重置前置运算符 返回引用是为了对同一个数进行操作MyInteger …

LSTM内部结构及前向传播原理——LSTM从零实现系列(1)

一、前言 作为专注于时间序列分析的玩家&#xff0c;虽然LSTM用了很久但一直没有写过一篇自己的LSTM原理详解&#xff0c;所以这次要写一个LSTM的从0到1的系列&#xff0c;从模型原理讲解到最后不借助三方框架自己手写代码来实现LSTM模型。本文本身没有特别独到之处&#xff0c…

Vue学习:el 与data的两种写法

el两种写法 法一&#xff1a;建立了联系 <!-- 准备容器 --><div id"root"><h1>hello,{{name}} </h1> <!-- {{插值语法}} --></div><script>new Vue({ el: #root,data: {name:Amy},});</script> 法二&#xff1a…

论文投稿指南——中国(中文EI)期刊推荐(第1期)

&#x1f680; EI是国际知名三大检索系统之一&#xff0c;在学术界的知名度和认可度仅次于SCI&#xff01;&#x1f384;&#x1f388; 【前言】 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊。其中&#xf…

java计算机毕业设计ssm特大城市地铁站卫生防疫系统5i80c(附源码、数据库)

java计算机毕业设计ssm特大城市地铁站卫生防疫系统5i80c&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持…

UDS服务基础篇之14

前言 你知道如果系统产生了DTC&#xff0c;应当如何清除呢&#xff1f;14服务具体的执行流程如何&#xff1f;14服务在使用过程中的常见bug又有哪些&#xff1f; 这篇&#xff0c;我们来一起探索并回答这些问题。为了便于大家理解&#xff0c;以下是本文的主题大纲&#xff1…

相控阵天线(十二):天线校准技术仿真介绍之旋转矢量法

目录简介旋转矢量法算法介绍旋转矢量法校准对方向图的影响旋转矢量法算法仿真移相器位数对旋转矢量法的影响多通道旋转矢量法算法仿真分区旋转矢量法算法仿真简介 由于制造公差和天线互耦的影响&#xff0c;天线各通道会呈现出较大的幅相误差&#xff0c;因此需对天线进行校准…

光阑,像差和成像光学仪器

人眼 人眼成像过程 空气-角膜 水状液-晶状体 晶状体-玻璃体 三个界面的折射成像 瞳孔 2-8mm 可变光阑,调节入射光强弱 睫状肌 改变晶状体曲率---调焦 人眼的调节 远点—眼睛完全松弛状态下看清楚的最远点&#xff0c;正常眼的远点在无穷远 近点—睫状肌最大收缩(焦…

【Redis】解决全局唯一 id 问题

永远要记得坚持的意义 一、全局唯一 id 场景 概念&#xff1a; 以订单表的 id 为例 使用自增 id 会产生的问题&#xff1a; id 的规律性太明显&#xff0c;容易让用户猜测到一些信息受表单数据量的限制 —— 分布式存储时&#xff0c;会产生问题 &#xff08;自增长&#x…

讲理论,重实战!阿里内部SpringBoot王者晋级之路全彩小册开源

大家都知道&#xff0c;Spring Boot框架目前不仅是微服务框架的最佳选择之一&#xff0c;还是现在企业招聘人才肯定会考察的点&#xff1b;很多公司甚至已经将SpringBoot作为了必备技能。但&#xff0c;现在面试这么卷的情况下&#xff0c;很多人面试时还只是背背面试题&#x…

基于KDtree的电路故障检测算法的MATLAB仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 k-d树是每个节点都为k维点的二叉树。所有非叶子节点可以视作用一个超平面把空间分割成两个半空间。节点左边的子树代表在超平面左边的点&#xff0c;节点右边的子树代表在超平面右边的点。选择超…

企业数据安全如何落实?私有化知识文档管理系统效率部署

编者按&#xff1a;本文分析了数据安全性企业的重要性&#xff0c;特别是高保密企业单位&#xff0c;介绍了天翎知识文档管理群晖NA是如何保护企业数据安全的。 关键词&#xff1a;私有化部署&#xff0c;安全技术&#xff0c;数据备份&#xff0c;病毒防护&#xff0c;全网隔…

【zeriotier】win10安装zeriotier的辛酸泪

目录概述问题1&#xff1a;waiting for zeriotier system service问题2&#xff1a;Zerotier One 出现Node ID “unknown”问题3&#xff1a;一切正常&#xff0c;但是连不上服务器最终解决方法附录概述 背景&#xff1a;实验室的服务器是使用zeriotier组网的&#xff0c;因此…

字符串-模板编译

模板编译 编译就是一种格式转换成另一种格式的过程&#xff0c;这里主要讨论一下模板编译。模板字符串对比普通的字符串有很多的不同&#xff0c;模板字符串可以嵌套&#xff0c;并且模板字符串可以在内部使用${xxx}进行表达式运算以及函数调用&#xff0c;这些其实都是模板编…

DPDK Ring

无锁环ring是DPDK提供的一种较为基础的数据结构&#xff0c;其支持多生产者和多消费者同时访问。 经过我的经验&#xff0c;无锁结构的实现主要依靠两方面&#xff1a; 最终的数据交换一定要是原子级的操作&#xff0c;最常用到的自然就是比较后交换&#xff08;Compare And S…

Java项目:SSM个人博客网站管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目包含管理员与游客两种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 发表文章,查看文章,类别管理,添加类别,个人信息管理,评论…

DeepSort目标跟踪算法

DeepSort目标跟踪算法是在Sort算法基础上改进的。 首先介绍一下Sort算法 Sort算法的核心便是卡尔曼滤波与匈牙利匹配算法 卡尔曼滤波是一种通过运动特征来预测目标运动轨迹的算法 其核心为五个公式&#xff0c;包含两个过程&#xff1a; 其分为先验估计&#xff08;预测&…

[附源码]计算机毕业设计人事管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

UE4中抛体物理模拟UProjectileMovementComponent

UE4中抛体物理模拟UProjectileMovementComponent1.简述2.使用方法3.绘制抛物曲线4.绘制抛物曲线1.简述 背景&#xff1a;实现抛体运动&#xff0c;反弹效果&#xff0c;抛物曲线等功能 通用实现可以使用spline绘制&#xff0c;物体按照下图接口可以根据时间更新位置 USplineC…