当前位置: 首页 > news >正文

React源码解析18(6)------ 实现useState

摘要

在上一篇文章中,我们已经实现了函数组件。同时可以正常通过render进行渲染。

而通过之前的文章,beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。

实现之前,我们要先修改一下我们的index.js文件:

import jsx from '../src/react/jsx.js'
import ReactDOM from '../src/react-dom/index'
import { useState } from './react-dom/filberHook.js';const root = document.querySelector('#root');function App() {const [name, setName] = useState('kusi','key');window.setName = setName;const [age, setAge] = useState(20)window.setAge = setAge;return jsx("div", {ref: "123",children: jsx("span", {children: name + age})});
}ReactDOM.createRoot(root).render(<App />)

由于我们这一篇并不会实现React的事件机制,所以我们先将setState的方法挂载在window上进行调试。有了基础,我们现在开始实现useState。

1.renderWithHook

在实现之前,我们先来思考一个问题。在之前实现beginWork机制的时候,我们为了兼容函数组件。获取子FilberNode的时候,函数组件是直接调用拿到返回值。
那么如果函数直接调用,是不是就已经调用了我们在函数里写的Hook。
所以我们把这一部分拆出来:

function updateFunctionComponent(filberNode) {const nextChildren = renderWithHook(filberNode);const newFilberNode = reconcileChildren(nextChildren);filberNode.child = newFilberNode;newFilberNode.return = filberNode;beginWork(newFilberNode)
}

在更新函数节点的时候,通过renderWithHook拿到函数执行的返回值:
那我们在renderWithHook里除了拿到函数执行的返回值,还要做什么呢?

这里值得注意的是,我们知道通过setState,函数组件会重新执行渲染。在这里,我们将函数的执行分为两种:第一次mount和后面的update。

就是执行useState这个过程,要分为两种,一种是mount下的useState,一种是update下的useState。OK,现在我们用一个标志去表示这两种状态,并且在renderWithHook下去改变它。

let hookWithStatus;
let workInPropgressFilber = null;export const renderWithHook = (filberNode) => {if(filberNode.child){//更新hookWithStatus = 'update'}else{//mounthookWithStatus = 'mount'}workInPropgressFilber = filberNode;const nextChildren = filberNode.type();return nextChildren;
}

2.实现mountState和Hook结构

现在我们在beginWork执行完后,会执行renderWithHook,执行后会改变hookWithStatus这个标志。再然后就是调用函数本身了。
所以现在我们根据这个标志实现两种不同的useState:

export const useState = (state) => {if(hookWithStatus === 'mount'){return mountState(state)}else if(hookWithStatus === 'update'){return updateState(state)}
}

也就是页面第一次渲染时,执行函数组件里的内容,我们要调用mountState。现在我们实现mountState。

实现之前,我们先说一下在React中,是如何将组件中的Hook存储的。在React中是通过链表的方式,将不同的Hook存储起来。现在我们定义一下Hook的结构:

它具有三个属性。memoizedState表示存储的state值,updateQueue表示需要更新的值,next表示指向的下一个hook。

class Hook {constructor(memoizedState, updateQueue, next){this.memoizedState = memoizedStatethis.updateQueue = updateQueuethis.next = next;}
}

所以在mountStaet中,我们要将这个链表结构实现出来:
这里我们定义一个headHook指向最外层的hook,workinProgressHook指向当前的hook。

function mountState(state) {const memoizedState = typeof state === 'function' ? state() : state;const hook = new Hook(memoizedState);hook.updateQueue= createUpdateQueue()if(workInPropgressHook === null){workInPropgressHook = hook;headHook = hook;}else{workInPropgressHook.next = hook;workInPropgressHook = hook;}return [memoizedState]
}

现在我们可以看一下HOOK的结构:

在这里插入图片描述
可以看出它是一个链表的结构,memoizedState保存的就是setState的初始值。

3.实现dispach更新

现在经过mount阶段后,我们已经有了一个基本的Hook链表。现在如果我在window下调用setState,那肯定是什么都不会发生的。

所以我们要实现setState方法,但是要调用setState方法是一定要更新的,所以我们将beginWork中的updateContainer方法修改一下,并且暴露出来:

function updateContainer(root, element) {const hostRootFilber = root.current;const update = createUpdate(element);hostRootFilber.updateQueue = createUpdateQueue()enqueueUpdate(hostRootFilber.updateQueue, update);wookLoop(root,hostRootFilber)
}export const wookLoop = (root,hostRootFilber) => {if(!hostRootFilber){hostRootFilber = root.current}beginWork(hostRootFilber);completeWork(hostRootFilber);root.finishedWork = hostRootFilber;console.log(root)commitWork(root)
}

这样我就可以在hook的机制里面调用wookLoop了。现在我们实现dispatch:

function disaptchState(filber, hook, action) {const update = createUpdate(action);enqueueUpdate(hook.updateQueue, update);workUpdateHook = hook;wookLoop(filber.return.stateNode)
}

dispatchState方法传入当前的filberNode, 还有就是对应的hook,以及需要更新的action。
同时我们将准备更新的hook进行标记。

所以在mountState中:

function mountState(state) {const memoizedState = typeof state === 'function' ? state() : state;const hook = new Hook(memoizedState);//其他代码。。。const disaptch = disaptchState.bind(null,workInPropgressFilber,hook)return [memoizedState,disaptch]
}

我们将dispatch需要的参数传进去,并且只给外面放开action。这样就实现好了dispatch方法。

4.实现updateState方法

当我们将上面的过程实现完之后,如果在控制台调用setState。那么就会触发workLoop,同时会再走一次beginWork。
此时再进入renderWithHook之后,就不会再走mountState了,而是进入updateState。

而在updateState中,我们要做的事情也不是很复杂,只需要从头遍历Hook链表,如果是标记更新的Hook,就返回更新的内容。如果不是,就正常返回它的memoizedState就好了。

function updateState(state) {if(currentHook === workUpdateHook){const newHook = new Hook(workUpdateHook.updateQueue.shared.pending.action)newHook.updateQueue = createUpdateQueue();const disaptch = disaptchState.bind(null,workInPropgressFilber,newHook)currentHook = currentHook.next;const result = [workUpdateHook.updateQueue.shared.pending.action,disaptch];return result;}else{let result = currentHook.memoizedState;const disaptch = disaptchState.bind(null,workInPropgressFilber,currentHook)currentHook = currentHook.next;return [result,disaptch]}
}

所以这也是为什么,在React中,不能在条件语句里面使用Hook,如果你mountState生成的Hook链表会发生变化。那么在updateState里面,遍历链表的时候,就会出现值错位的情况。

OK,到这里useState的方法也已经实现完了。

相关文章:

React源码解析18(6)------ 实现useState

摘要 在上一篇文章中&#xff0c;我们已经实现了函数组件。同时可以正常通过render进行渲染。 而通过之前的文章&#xff0c;beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。 实现之前&#xff0c;我们要先修改一下我们的index.js文件&#x…...

MongoDB的下载和安装

一、MongoDB下载 下载地址&#xff1a;https://www.mongodb.com/try/download/community 二、安装 因为选择下载的是 .zip 文件&#xff0c;直接跳过安装&#xff0c;一步到位。 选择在任一磁盘创建空文件夹&#xff08;不要使用中文路径&#xff09;&#xff0c;解压之后把…...

如何卖 Click to WhatsApp 广告最有效

2022年&#xff0c;大多数直接面向消费者的品牌都面临相同挑战—— Facebook 和 Instagram 的广告成本大幅增加。Business Insider 报导指出&#xff0c;2021年 Facebook 广告每次点击的平均成本&#xff08;average cost per click&#xff09;达到0.974美元&#xff0c;按年升…...

【UE4 RTS】10-RTS HUD Setup

前言 本篇博文主要制作了一个控件蓝图界面&#xff0c;用于显示当前的游戏时间 效果 步骤 1. 新建一个名为“Widgets”的文件夹 在该文件夹中新建一个控件蓝图&#xff0c;命名为“GameTime_HUD” 打开“GameTime_HUD”&#xff0c;添加如下控件 2. 打开玩家控制器“RTS_Pla…...

Python生成指定大小文件:txt/图片/视频/csv

如题&#xff0c;做测试的懂的都懂&#xff0c;不多解释 相比其他大佬&#xff0c;本脚本基于gpt编写后整理&#xff0c;生成的文件更真实&#xff0c;能够打开预览&#xff0c;看过其他人的生成脚本&#xff0c;只是一个符合大小&#xff0c;但是是空白或不能打开的文件。 话…...

Arcgis中影像图切片有白斑或者白点

效果 步骤 1、3dmax渲染或者其它原片 2、Arcgis中加载图片 原数据效果 3、定义投影和转换坐标系等等 我这边测试数据是EPSG:4326的坐标系 4、导出jp2(JPG2000)格式 转换后效果 5、发布服务 6、效果对比...

nlohmann json:通过[ ]运算符读取设置object/array

除了可以通过at,还可以通过[ ]运算符来读取和设置object/array #include <iostream> #include <nlohmann/json.hpp> using namespace std; using json = nlohmann::json;int main() {json data = R"({"name": "xiaoming","age&quo…...

rust学习-tokio::time

示例 use std::time::Duration; use tokio::{task, time::interval};#[tokio::main] async fn main() {let mut interval interval(Duration::from_secs(1));let handle task::spawn(async move {loop {interval.tick().await;println!("tick");}});handle.await.…...

Java 中 List 集合排序方法

方式一&#xff1a; 调用List接口自己的sort方法排序 public static void main(String[] args) {List<Integer> numListnew ArrayList<>();numList.add(999);numList.add(123);numList.add(456);numList.add(66);numList.add(9);Collections.sort(numList); //使…...

prometheus监控k8s服务并告警到钉钉

一、监控k8s集群 要监控k8s集群需要使用到以下服务用于收集监控的资源信息&#xff0c;node_exporter用于监控k8s集群节点的资源信息&#xff0c;kube-state-metrics用于监控k8s集群的deployment、statefulset、daemonset、pod等的状态&#xff0c;cadvisor用于监控k8s集群的p…...

Go和Java实现解释器模式

Go和Java实现解释器模式 下面通过一个四则运算来说明解释器模式的使用。 1、解释器模式 解释器模式提供了评估语言的语法或表达式的方式&#xff0c;它属于行为型模式。这种模式实现了一个表达式接口&#xff0c;该接口 解释一个特定的上下文。这种模式被用在 SQL 解析、符…...

域名配置HTTPS

一、注册域名 这个可以在各大平台注册&#xff0c;具体看一下就会注册了&#xff0c;自己挑选一个自己喜欢的域名。 步骤一般也就是先实名&#xff0c;实名成功了才能注册域名。 二、办理SSL证书 这里使用的是阿里云的SSL免费证书 1、申请证书 二、填写申请 三、域名绑定生…...

机械设计cad,ug编程设计,ug模具设计,SolidWorks模具设计

模具设计培训课程&#xff1a; 【第一阶段&#xff1a;CAD识图制图】 [AutoCAD机械制图]&#xff1a;全面讲解AUTOCAD应用知识&#xff0c;常用命令讲解与运用&#xff0c;二维平面图绘制&#xff0c;三维成型零件的绘制与设计&#xff0c;制作工程图 【第二阶段&#xff1a;U…...

嵌入式开发的学习与未来展望:借助STM32 HAL库开创创新之路

引言&#xff1a; 嵌入式开发作为计算机科学领域的重要分支&#xff0c;为我们的日常生活和产业发展提供了无限的可能。STMicroelectronics的STM32系列芯片以其出色的性能和广泛的应用领域而备受关注。而STM32 HAL库作为嵌入式开发的高级库&#xff0c;为学习者提供了更高效、更…...

WPS-0DAY-20230809的分析和利用复现

WPS-0DAY-20230809的分析和初步复现 一、漏洞学习1、本地复现环境过程 2、代码解析1.htmlexp.py 3、通过修改shellcode拿shell曲折的学习msf生成sc 二、疑点1、问题2、我的测试测试方法测试结果 一、漏洞学习 强调&#xff1a;以下内容仅供学习和测试&#xff0c;一切行为均在…...

MongoDB(三十九)

目录 一、概述 &#xff08;一&#xff09;相关概念 &#xff08;二&#xff09;特性 二、应用场景 三、安装 &#xff08;一&#xff09;编译安装 &#xff08;二&#xff09;yum安装 1、首先制作repo源 2、软件包名&#xff1a;mongodb-org 3、启动服务&#xff1a…...

InnoDB引擎

1 逻辑存储结构 InnoDB的逻辑存储结构如下图所示: 1). 表空间 表空间是InnoDB存储引擎逻辑结构的最高层&#xff0c; 如果用户启用了参数 innodb_file_per_table(在8.0版本中默认开启) &#xff0c;则每张表都会有一个表空间&#xff08;xxx.ibd&#xff09;&#xff0c;一个…...

CSS3中的var()函数

目录 定义&#xff1a; 语法&#xff1a; 用法&#xff1a; 定义&#xff1a; var()函数是一个 CSS 函数用于插入自定义属性&#xff08;有时也被称为“CSS 变量”&#xff09;的值 语法&#xff1a; var(custom-property-name, value) 函数的第一个参数是要替换的自定义属性…...

opencv图片换背景色

#include <iostream> #include<opencv2/opencv.hpp> //引入头文件using namespace cv; //命名空间 using namespace std;//opencv这个机器视觉库&#xff0c;它提供了很多功能&#xff0c;都是以函数的形式提供给我们 //我们只需要会调用函数即可in…...

JAVA语言:什么是懒加载机制?

JVM没有规定什么时候加载,一般是什么时候使用这个class才会什么时候加载,但是JVM规定了什么时候必须初始化(初始化是第三步、装载、连接、初始化),只要加载之后,那么肯定是要进行初始化的,所以我们就可以通过查看这个类有没有进行初始化,从而判断这个类有没有被加载。 …...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...