【从0实现React18】 (四) 如何触发更新 带你了解react触发更新的流程以及更新后如何触发render

常见的触发更新的方式

  • 创建 React 应用的根对象 ReactDOM.creatRoot().render()
  • 类组件 this.setState()
  • 函数组件 useState useEffect

我们希望实现一套统一的更新机制,他的特点是:

  • 兼容上述触发更新的方式
  • 方便后续拓展(优先级机制)

更新机制的组成部分

  • 代表更新的数据结构 Update
  • 消费update的数据结构——UpdateQueue

实现 Update 和 UpdateQueue

packages/react-reconciler/src/ 目录下新建 updateQueue.ts 文件:

 // packages/react-reconciler/src/updateQueue.ts
import { Action } from '@/shared/ReactTypes'
import { Update } from './fiberFlags'export interface Update<State> {action: Action<State>
}export interface UpdateQueue<State> {shared: {pending: Update<State> | null}
}/** 创建 Update */
export const createUpdate = <State>(action: Action<State>): Update<State> => {return {action,}
}/** 创建 UpdateQueue */
export const createUpdateQueue = <Action>() => {return {shared: {pending: null,},} as UpdateQueue<Action>
}/** updateQueue添加update */
export const enqueueUpdate = <Action>(updateQueue: UpdateQueue<Action>,update: Update<Action>
) => {updateQueue.shared.pending = update
}/** updateQueue消费update */
export const processUpdateQueue = <State>(baseState: State, // 初始状态pendingUpdate: Update<State> | null // 要消费的Update
): { memoizedState: State } => {const result: ReturnType<typeof processUpdateQueue<State>> = {memoizedState: baseState,}if (pendingUpdate !== null) {const action = pendingUpdate.actionif (action instanceof Function) {// baseState:1, update:(x)=>4*x  ——> memoizedState:4result.memoizedState = action(baseState)} else {// baseState:1, update:2  ——>  memoizedState:2result.memoizedState = action}}return result
}
 // packages/shared/ReactTypes.ts
// .../** useState支持的两种dispatch参数 */ export type Action<State> = State | ((prevState: State) => State) 

实现触发更新

接下来的工作包括:

  • 实现 ReactDOM.createRoot().render()时调用的API
  • 将该API接入上述更新机制中

需要考虑的事情:

  • 更新可能发生于任意组件,而更新流程是从根节点递归
  • 需要一个统一的根节点保存通用信息

  • ReactDOM.createRoot() 函数生成一个新的 Root 对象,它在源码中是 FiberRootNode 类型,充当了 React 应用的根节点。
  • rootElement 则是要渲染到的 DOM 节点,它在源码中是 hostRootFiber 类型,作为 React 应用的根 DOM 节点。
  • render() 方法将组件 <App/> 渲染到根节点上。在这个过程中,React 会创建一个代表 <App/> 组件的 FiberNode,并将其添加到 Root 对象的 Fiber 树上。

实现 FiberRootNode

根据上图,我们先来实现 FiberRootNode 类型:

 // packages/react-reconciler/src/fiber.ts// ...
export class FiberRootNode {container: Container  // 保存挂载节点 FiberRootNodecurrent: FiberNode // 指向 hostRootFiberfinishedWork: FiberNode | null // 最后递归完成的 fiberconstructor(container: Container, hostRootFiber: FiberNode) {this.container = containerthis.current = hostRootFiberhostRootFiber.stateNode = thisthis.finishedWork = null}
}

实现 render 调用的 API

接着我们来实现 ReactDOM.createRoot().render() 过程中调用的 API:

  • createContainer 函数: 用于创建一个新的容器(container),该容器包含了 React 应用的根节点以及与之相关的一些配置信息。createContainer 函数会创建一个新的 Root 对象,该对象用于管理整个 React 应用的状态和更新。
  • updateContainer 函数: 用于更新已经存在的容器中的内容。在内部,updateContainer 函数会调用 scheduleUpdateOnFiber 等方法,通过 Fiber 架构中的协调更新过程,将新的 React 元素(element)渲染到容器中,并更新整个应用的状态。

新建文件 fiberReconciler.ts,里面有 createContainerupdateContainer 两个函数

import { Container } from 'hostConfig'
import { FiberNode, FiberRootNode } from './fiber'
import { HostRoot } from './workTags'
import {UpdateQueue,createUpdate,createUpdateQueue,enqueueUpdate,
} from './updateQueue'
import { ReactElementType } from 'shared/ReactTypes'export function createContainer(container: Container) {// 1.新建 hostRootFiberconst hostRootFiber = new FiberNode(HostRoot, {}, null)// 2.新建 fiberRootNodeconst root = new FiberRootNode(container, hostRootFiber)// 3.初始化hostRootFiber的updateQueuehostRootFiber.updateQueue = createUpdateQueue()return root
}export function updateContainer(element: ReactElementType | null,root: FiberRootNode
) {// 1.获取 hostRootFiberconst hostRootFiber = root.current// 2.新建 updateconst update = createUpdate<ReactElementType | null>(element)// 3.将该 update 插入到 hostRootFiber的updateQueue中enqueueUpdate(hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,update)return element
}

实现在 updateContainer后进入wordLoop更新流程

将updateContainer方法与wordLoop的renderRoot更新流程连接:

workLoop.ts 文件中实现 scheduleUpdateOnFiber函数:

/** 实现在 updateContainer后进入wordLoop更新流程 */
export function scheduleUpdateOnFiber(fiber: FiberNode) {// TODO 调度功能// 先找到触发更新节点的根节点const root = markUpdateFromFiberToRoot(fiber)// 然后执行 renderRootrenderRoot(root)
}

然后在updateContainer中执行scheduleUpdateOnFiber(hostRootFiber)

export function updateContainer(element: ReactElementType | null,root: FiberRootNode
) {// ...// 进入wordLoop更新流程scheduleUpdateOnFiber(hostRootFiber)return element
}

另外,在上一节中,我们在实现 prepareFreshStack 函数时,直接将 root 作为参数赋值给了 workInProgress,但现在我们知道了,root 其实是 FiberRootNode 类型的,不能直接赋值给 FiberNode 类型的 workInProgress,所以需要写一个 createWorkInProgress 函数处理一下:

// fiber.ts 
/** 创建 WorkInProgress*/
export const createWorkInProgress = (current: FiberNode,pendingProps: Props
): FiberNode => {let wip = current.alternateif (wip === null) {// mountwip = new FiberNode(current.tag, pendingProps, current.key)wip.stateNode = current.stateNodewip.alternate = currentcurrent.alternate = wip} else {// updatewip.pendingProps = pendingPropswip.flags = NoFlags // 清除副作用}wip.type = current.typewip.updateQueue = current.updateQueuewip.children = current.childrenwip.memoizedProps = current.memoizedPropswip.memoizedState = current.memoizedStatereturn wip
}

然后更新wordLoop初始化:

// workLoop.ts
/** 初始化 */
function prepareFreshStack(root: FiberRootNode) {workInProgress = createWorkInProgress(root.current, {})
}

至此,我们已经实现了 React 应用在首次渲染或后续更新时的大致更新流程,一起来回顾一下:

  • 首先,我们通过 createContainer 函数创建了 React 应用的根节点 FiberRootNode,并将其与 DOM 节点(hostFiberRoot)连接起来;

  • 然后,通过 updateContainer 函数创建了一个更新(update),并将其加入到更新队列(updateQueue)中,启动了首屏渲染或后续更新的机制;
  • 接着会调用 scheduleUpdateOnFiber 函数开始调度更新,从触发更新的节点开始向上遍历,直到达到根节点 FiberRootNode
  • 接着会调用 renderRoot 函数,初始化 workInProgress 变量,生成与 hostRootFiber 对应的 workInProgress
  • 接着就开始 Reconciler 的更新流程,即 workLoop 函数,对 Fiber 树进行深度优先遍历(DFS);
  • 在向下遍历阶段会调用 beginWork 方法,在向上返回阶段会调用 completeWork 方法,这两个方法负责 Fiber 节点的创建、更新和处理,具体实现会在下一节会讲到。

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

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

相关文章

c++学习-----内存管理

1. C/C内存分布 我们先来看下面的一段代码和相关问题 答案揭晓&#xff1a; 这里很多人会误认为*char2在常量区&#xff0c;这其实是错误的 因为&#xff1a; 首先在内存字符常量区分配一块内存空间放下”abcd\0”&#xff0c;然后在栈中分配一块连续的内存空间&#xff0c;…

SQL-Python

师从黑马程序员 数据库介绍 数据库就是存储数据的库 数据组织&#xff1a;库->表->数据 数据库和SQL的关系 MySQL的基础命令 SQL基础 SQL语言的分类 SQL的语法特征 DDL-库管理 show DATABASES;use sys;SELECT database();CREATE DATABASE test CHARSET utf-8;SHOW D…

学习C++第二天

1.缺省参数 缺省参数的概念&#xff1a; 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 void show(int a 10) {cout << a << endl; }int main() {sho…

ubuntu18.04 编译HBA 并实例运行

HBA是一个激光点云层级式的全局优化的程序&#xff0c;他的论文题目是&#xff1a;HBA: A Globally Consistent and Efficient Large-Scale LiDAR Mapping Module&#xff0c;对应的github地址是&#xff1a;HKU-Mars-Lab GitHub 学习本博客&#xff0c;可以学到gtsam安装&am…

提升Python技能的七个函数式编程技巧

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 递归📝 结构化模式匹配📝 不变性📝 纯函数📝 高阶函数📝 函数组合📝 惰性求值⚓️ 相关链接 ⚓️📖 介绍 📖 在现代编程中,虽然Python并不是一门纯粹的函数式编程语言,但函数式编程(Funct…

Linux C/C++ socket函数

目录 socket函数 函数原型 头文件 功能 返回值 参数 错误码 socket函数 函数原型 int socket(int domain, int type, int protocol); 头文件 #include <sys/types.h> #include <sys/socket.h> 功能 创建一个用于通信的端点&#xff0c;并返回一个文件描述符…

登录安全分析报告:链家地产

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

力扣刷题 杨辉三角(使用c++ vector解法)

杨辉三角 题目描述示例1示例2提示:代码 题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例1 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例2 …

上位机图像处理和嵌入式模块部署(mcu和swd接口)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 最近学习mcu的时候&#xff0c;接触了不少调试器&#xff0c;这里面有daplink、st-link v2、j-link v9。虽然模块的形状可能不太一样&#xff0c;但…

基于I2C协议的AHT20温湿度传感器的数据采集

一、I2C总线通信协议 软件I2C 软件I2C&#xff0c;也称为模拟I2C或bit-bang I2C&#xff0c;是一种通过微控制器的通用输入输出&#xff08;GPIO&#xff09;引脚来模拟I2C总线通信的方式。它不依赖于专门的硬件I2C接口&#xff0c;而是通过编程控制GPIO引脚的电平状态来实现I…

快去复习吧+++常用算法及参考算法 递推法++穷举法++排序(冒泡、选择)++查找(顺序、折半)++字符串处理++方程求根++无穷级数求和

接上&#xff1a;常用算法及参考算法 &#xff08;1&#xff09;累加 &#xff08;2&#xff09;累乘 &#xff08;3&#xff09;素数 &#xff08;4&#xff09;最大公约数 &#xff08;5&#xff09;最值问题 &#xff08;6&#xff09;迭代法 常用算法及参考算法 7. 递推法…

Vue-观察器(watch)的定义方式引发组件初始值没有渲染成功问题(已解决)

问题描述&#xff1a;在测试环境发现一个问题&#xff0c;打开一张表单的时候&#xff0c;所有字段都成功赋上了值&#xff0c;唯独一个人员组件的值&#xff08;出差人员&#xff09;没有带出&#xff0c;而接口返回的数据是正常的&#xff0c;也就是说不是后端接口的问题&…

JVM专题七:JVM垃圾回收机制

JVM专题六&#xff1a;JVM的内存模型中&#xff0c;我们介绍了JVM内存主要分哪些区域&#xff0c;这些区域分别是干什么的&#xff0c;同时也举了个例子&#xff0c;在运行过程种各个区域数据是怎样流转的。细心的小伙伴可能发现一个问题&#xff0c;在介绍完方法弹栈以后就没有…

指令微调数据集构建方法

指令微调&#xff08;Instruction Tuning&#xff09;&#xff0c;是指使用自然语言形式的数据对预训练后的大语言模型进行参数微调&#xff0c;在一些文章中也称为有监督微调&#xff08;Supervised Fine-tuning&#xff0c;SFT&#xff09;或多任务提示训练&#xff08;Multi…

CARLA自动驾驶模拟器基础

CARLA 使用服务器-客户端架构运行&#xff0c;其中 CARLA 服务器运行模拟并由客户端向其发送指令。客户端代码使用 API 与服务器进行通信。要使用 Python API&#xff0c;您必须通过 PIP 安装该模块&#xff1a; pip3 install carla-simulator # Python 3World and client 客…

React18中各种Hooks用法总结( 内附案例讲解)

React中各种Hooks用法总结 内附案例讲解 一、useState useState 是一个 React Hook&#xff0c;它允许你向组件添加一个 状态变量。 import React, { FC, memo, useState } from react import { MainContainer } from ./style interface IProps {children?: React.ReactNo…

FRP内网穿透及多级代理的使用

目录 0、前言 1、场景介绍 2、环境准备 2.1 下载frp 2.2 配置一台VPS 2.3 socks5客户端 2.5 网络环境准备 3、Frp设置 3.1 一层代理 3.1 二层代理 4、Frp总结 0、前言 FRP是比较老牌的也是比较流行的反向代理、内网穿透软件。FRP用途和使用场景可以看官方文档&#xff0c;…

JavaScript 预编译与执行机制解析

在深入探讨JavaScript预编译与执行机制之前&#xff0c;我们首先需要明确几个基本概念&#xff1a;声明提升、函数执行上下文、全局执行上下文以及调用栈。这些概念共同构成了JavaScript运行时环境的核心组成部分&#xff0c;对于理解代码的执行流程至关重要。本文将围绕这些核…

美团携手HarmonyOS SDK,开启便捷生活新篇章

华为开发者大会&#xff08;HDC 2024&#xff09;于6月21日在东莞松山湖拉开序幕&#xff0c;通过一系列精彩纷呈的主题演讲、峰会、专题论坛和互动体验&#xff0c;为开发者们带来了一场知识与技术的盛宴。6月23日&#xff0c;《HarmonyOS开放能力&#xff0c;使能应用原生易用…

24-6-23-读书笔记(七)-《文稿拾零》豪尔赫·路易斯·博尔赫斯(第三辑)

文章目录 《文稿拾零》阅读笔记记录总结 《文稿拾零》 《文稿拾零》超厚的一本书&#xff08;570&#xff09;&#xff0c;看得时间比较长&#xff0c;这本书是作者零散时间写的一些关于文学性质的笔记&#xff0c;读起来还是比较无趣的&#xff0c;非常零散&#xff0c;虽然有…