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的方法也已经实现完了。

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

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

相关文章

DAY2,ARM(特殊功能寄存器,数据操作指令,跳转指令)

1.cmp、sub、b指令的使用&#xff1b; 代码&#xff1a; .text .global _start _start:mov r0,#9mov r1,#15loop:cmp r0,r1beq stopsubcc r1,r1,r0subhi r0,r0,r1b loopstop:b stop .end结果&#xff1a; 2.汇编指令计算1~100之间和&#xff1b; 代码&#xff1a; .text .gl…

【从零学习python 】47. 面向对象编程中的继承概念及基本使用

文章目录 继承的基本使用代码逐行讲解说明:进阶案例 继承的基本使用 在现实生活中&#xff0c;继承一般指的是子女继承父辈的财产&#xff0c;父辈有的财产&#xff0c;子女能够直接使用。 程序里的继承 继承是面向对象软件设计中的一个概念&#xff0c;与多态、封装共为面向对…

培训报名小程序-用户注册

目录 1 创建数据源2 注册用户3 判断用户是否注册4 完整代码总结 我们的培训报名小程序&#xff0c;用户每次打开时都需要填写个人信息才可以报名&#xff0c;如果用户多次报名课程&#xff0c;每次都需要填写个人信息&#xff0c;比较麻烦。 本篇我们就优化一下功能&#xff0c…

线上售楼vr全景看房成为企业数字化营销工具

在房地产业中&#xff0c;VR全景拍摄为买家提供了虚拟看房的全新体验。买家可以通过相关设备&#xff0c;远程参观各个楼盘的样板间和实景&#xff0c;感受房屋的空间布局和环境氛围&#xff0c;极大地提高了购房决策的准确性。对于房地产开发商和中介机构来说&#xff0c;VR全…

如何搭建个人邮件服务hmailserver并实现远程发送邮件

文章目录 1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网映射工…

计算机竞赛 GRU的 电影评论情感分析 - python 深度学习 情感分类

1 前言 &#x1f525;学长分享优质竞赛项目&#xff0c;今天要分享的是 &#x1f6a9; GRU的 电影评论情感分析 - python 深度学习 情感分类 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 这…

代码随想录算法训练营第三十八天 | 理论基础,509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯

代码随想录算法训练营第三十八天 | 理论基础&#xff0c;509. 斐波那契数&#xff0c;70. 爬楼梯&#xff0c;746. 使用最小花费爬楼梯 理论基础什么是动态规划动态规划的解题步骤动态规划应该如何debug 509. 斐波那契数递归解法 70. 爬楼梯746. 使用最小花费爬楼梯 理论基础 视…

计蒜客T1170——人民币支付

超级水&#xff0c;不解释&#xff0c;代码的处理方式减低了繁琐程度&#xff0c; #include <iostream> using namespace std;int main(int argc, char** argv) {int num0;cin>>num;int money[6]{100,50,20,10,5,1};for(int i0;i<5;i){int count0;countnum/mone…

C++超基础语法

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

IDEA常用工具配置

IDEA常用工具&配置 如果发现插件市场用不了&#xff0c;可以设置Http Proxy&#xff0c;在该界面上点击”Check connection“并输入的地址&#xff1a;https://plugins.jetbrains.com/ 。 一、常用插件 1、MybatisX Mybaits Plus插件&#xff0c;支持java与xml互转 2、F…

日志系统——日志格式化模块设计

一&#xff0c;模块主要成员 该模块的主要作用是对日志消息进行格式化&#xff0c;将日志消息组织成制定格式的字符串。 该模块主要成员有两个&#xff1a;1.格式化字符串。 2.格式化子项数组 1.1 格式化字符串 格式化字符串的主要功能是保存日志输出的格式字符串。其格式化字…

WPF 界面结构化处理

文章目录 概要一、xaml界面结构化处理二、逻辑树与视觉树 概要 WPF 框架是开源的&#xff0c;但是不能跨平台&#xff0c;可以使用MAUI&#xff0c;这个框架可以跨平台&#xff0c;WPF源码可以在github上下载&#xff0c;下载地址&#xff1a;https://gitbub.com/dotnet/wpf。…

【C++ 记忆站】命名空间

文章目录 命名空间概念命名空间的定义1、正常的命名空间定义2、命名空间可以嵌套3、同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中 命名空间的使用1、加命名空间名称及作用域限定符2、使用using将命名空间中某个成员引入3、使用using namespac…

初试时间官宣!研招网发布下半年重要时间节点!今日速报来了

距24考研初试还有127天&#xff0c;今天给大家带来初试和报名时间官宣消息、考研报名注意事项、研招网发布的2024考研“保姆级”下半年重要时间节点。有用记得收藏 24考研报名和初试时间官宣 已有学校在招生简章中明确24考研初试时间 初试时间预计为&#xff1a;2023年12月23…

初试rabbitmq

rabbitmq的七种模式 Hello word 客户端引入依赖 <!--rabbitmq 依赖客户端--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.8.0</version></dependency> 生产者 imp…

邀请函|澎峰科技邀您参加CCF HPC China2023

一年一度的全球超算盛会&#xff01; 以“算力互联智领未来”为主题的第十九届全国高性能计算学术年会&#xff08;CCF HPC China 2023&#xff09;将于8月24-26日&#xff08;展览23-25日&#xff09;在青岛红岛国际会议展览中心举办。 九大院士领衔 打造顶级超算盛会 力邀…

《离散数学及其应用(原书第8版)》ISBN978-7-111-63687-8 第11章 11.1.3 树的性质 节 第664页的例9说明

《离散数学及其应用&#xff08;原书第8版&#xff09;》ISBN978-7-111-63687-8 第11章 11.1.3 树的性质 节 第664页的定理3的引申 定理3 带有i个内点的m叉树含有nmi1个顶点 见本人博文 内点定义不同的讨论 如果对于一个m叉正则树&#xff0c;即任意分支节点的儿子恰好有m个&am…

谈谈IP地址和子网掩码的概念及应用

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、IP地址的概念 二、IP地址的分类 1、A类 …

长胜证券:散户可以随大流吗?怎么做才好?

在我国的股市里边&#xff0c;最不缺的或许便是散户了&#xff0c;一方面&#xff0c;散户促进了股市的活泼&#xff0c;可一方面又特容易望风而动&#xff0c;追涨杀跌。因此&#xff0c;散户能够随大流吗&#xff1f;该怎么做才好&#xff1f;对于这些&#xff0c;长胜证券为…

IntelliJ IDEA热部署:JRebel插件的安装与使用

热部署 概述JRebel 概述 热部署&#xff0c;指修改代码后&#xff0c;无需停止应用程序&#xff0c;即可使修改后的代码生效&#xff0c;其有利于提高开发效率。 热部署方式&#xff1a; 手动热部署&#xff1a;修改代码后&#xff0c;重新编译项目&#xff0c;然后启动应用程…