React渲染机制及相关优化方案

news2024/9/29 21:32:26

React渲染机制及相关优化方案

  • 前言:
  • 一、react渲染步骤
  • 二、concurrent机制以及产生作用的机会
    • 1. 优先级调度:
    • 2. 递增式渲染:
  • 三、简单模拟实现 concurrent mode 的递增式渲染
  • 四、与优先级调度有关的两个hooks
    • 1. useTransition
    • 2. useDeferredValue
    • 3. useTransition 与 useDeferredValue 的区别
    • 4. 应用场景
  • 五、一个小例子
    • 1. 下面使用 useTransition 进行优化
    • 2. 使用 useDeferredValue 进行优化
  • 补充:为什么VUE不需要设计 Concurrent Mode


前言:

  • 文章主要简单记录react中的渲染机制以及相关的优化方案,内容包括如下:
  • react渲染步骤、concurrent机制以及产生作用的机会
  • 简单模拟实现 concurrent mode
  • 基于作业调度优先级的思路进行项目优化的两个hooks
  • 案例演示

一、react渲染步骤

  • 准备阶段(Prepare Phase)
    在准备阶段,React 会收集组件的依赖关系,建立组件树的数据结构,确定组件的更新优先级,并生成用于渲染的工作单元。

  • 计算阶段(Compute Phase)
    在计算阶段,React 会根据组件的更新优先级和调度策略,将工作单元分成多个批次进行处理。每个批次都会执行一小部分工作单元,以保证用户界面的响应性。

  • 渲染阶段(Render Phase)
    在渲染阶段,React 会根据工作单元的类型和优先级,执行相应的渲染操作。这包括创建新的虚拟 DOM 节点、更新现有的虚拟 DOM 节点,以及卸载不再需要的组件。

  • 提交阶段(Commit Phase)
    在提交阶段,React 会将更新后的虚拟 DOM 节点映射到实际的 DOM,更新用户界面。这个阶段还会执行一些副作用操作,如执行useEffect。

二、concurrent机制以及产生作用的机会

注:React 的并发模式(Concurrency Mode)是一种用于处理大型和复杂应用程序的特性,旨在提高应用程序的性能和响应能力。解决react中状态更新就会触发该组件及该组件下所有子组件无脑更新而引发的性能问题;同时提供部分控制作业调度优先级的能力给开发者使用

  • 在传统的 React 渲染模式中,更新操作是同步进行的,即在进行更新时,会立即进行组件的重新渲染,可能会阻塞主线程,导致页面响应变慢或失去响应出现掉帧问题。

  • 而concurrent mode通过引入一种新的调度算法和优先级机制,将更新操作划分为多个优先级,使得 React 可以更好地管理和分配任务,以实现更平滑的用户体验。

  • concurrent mode主要具备以下几个特性:异步渲染、优先级调度、递增式渲染

补充:concurrent mode 主要工作在渲染流程的 Compute Phase 及 Render Phase,因为它们是纯粹的 JS 计算意味着可以被拆分,而 commit 阶段由于带有 DOM 更新,不可能 DOM 变更到一半中断,因此必须一次性执行完成

1. 优先级调度:

concurrent mode 通过对任务进行优先级划分,React 可以根据优先级动态地分配和重新分配任务。基于此React 可以更好地响应用户交互和其他高优先级的任务,同时提供了 “useDeferredValue” 、“useTransition” 两个hooks用于调度作业任务的优先级。

2. 递增式渲染:

1)concurrent mode 下的渲染是逐步进行的,React 将大量需要重新渲染的组件的工作基于时间片的理念划分为多个小片段工作,在浏览器的每一帧的空闲时间中去执行这些渲染工作,而不是一下子全部直接执行,这样有效的避免了掉帧情况的出现。

2)这里也就说明了为什么React官方说 componentWillMount 可能被调用多次的原因,正是因为低优先级任务的 render 阶段可能被重复的中断和重新执行,而 componentWillMount 就包含在 render 阶段中。

注意:工作拆分的最小单元应该是一个组件,当某个组件本身的计算就十分巨大时依然会导致卡帧,不过我们可以通过调整工作的优先级使得用户的体验是平滑的

三、简单模拟实现 concurrent mode 的递增式渲染

  • 下面使用 requestIdleCallback 函数模拟时间片,在每一帧的空闲时间进行js计算从而达到递增式渲染的效果

index.html

<!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>
    <script src="index.js"></script>
</head>

<body>
    <div id="root"></div>
    <script>
        // 调用render提供挂载容器 "root"
        render(document.getElementById('root'))
    </script>
</body>

</html>

index.js

// 页面需要渲染的组件
function Counter() {
    return {
        type: 'span',
        value: 'hello world',
        next: {
            type: 'p',
            value: 'hello LiHua'
        }
    }
}
const CounterElementDescriptors = {
    type: 'Function',
    fn: Counter
}

// 记录当前工作
let presentWork = null
// 记录根元素
let rootElementDescriptore = null    
// 记录挂载容器 
let elementsContainer = null    

// 处理单元任务
function performUnitOfWork(deadline) {
    // 判断当前是否还有待执行任务
    if (presentWork == null) return commitRoot(rootElementDescriptore)

    // 当前帧超时,调用 requestIdleCallback 把任务推到下一帧空闲时间执行
    if (deadline.didTimeout) return requestIdleCallback(executeWorkLoop)

    // 若是组件则处理依赖关系、若是元素则生成真实dom
    if (presentWork.type === "Function") {
        rootElementDescriptore = presentWork
        const firstChildren = presentWork.fn()
        firstChildren.parent = presentWork
        presentWork.children = firstChildren
        presentWork = firstChildren
        performUnitOfWork(deadline)
    } else {
        const dom = document.createElement(presentWork.type)
        dom.innerHTML = presentWork.value
        presentWork.dom = dom
        presentWork = presentWork.next
        performUnitOfWork(deadline)
    }
}

// 控制循环执行工作
function executeWorkLoop(deadline) {
    performUnitOfWork(deadline)
}

// 提供render函数,用于获取挂载容器和开始渲染计算工作
function render(element) {
    elementsContainer = element
    presentWork = CounterElementDescriptors
    requestIdleCallback(executeWorkLoop)
}

// 模拟commit阶段
function commitRoot(rootElement) {
    let renderCHildrenElements = rootElement.children
    do {
        elementsContainer.appendChild(renderCHildrenElements.dom)
        renderCHildrenElements = renderCHildrenElements.next
    }while(renderCHildrenElements)
}

四、与优先级调度有关的两个hooks

1. useTransition

官方解释:useTransition 是一个让你在不阻塞 UI 的情况下来更新状态的 React Hook。

  • 通过 useTransition 我们可以将一部分的状态更新工作划分为低优先级的异步任务,使它不阻塞主要任务的执行
  • 同时我们可以依据 useTransition 返回的标志状态在渲染期间优雅地展示加载状态,从而提高用户界面的交互体验和流畅性
  • useTransition 主要语法如下:
import { useTransition } from "react";
function TabContainer() {
    // isPending 标志,告诉你是否存在待处理的低优先级工作。
    // startTransition 函数 允许你将该部分的状态更新标记为低优先级。
    const [isPending, startTransition] = useTransition();

    function handle() {
        startTransition(() => {
            // 低优先级的状态更新工作
            {......}
        });
    }

    return (
        {......}
    )
}

2. useDeferredValue

官方解释:useDeferredValue 是一个 React Hook,可以让你延迟更新 UI 的某些部分。

  • 通过 useDeferredValue 我们可以将一部分的UI更新工作划分为低优先级的任务,使它不阻塞主要任务的执行
  • useTransition 主要语法如下:
import { useDeferredValue, useState, } from "react";
function TabContainer() {
    const [query, setQuery] = useState('');
    // 定义的 deferredQuery 获取的是query的延迟版本
    const deferredQuery = useDeferredValue(query);

    function handle(data) {
        setQuery(data)
    }

    return (
        { ......}
    )
}

3. useTransition 与 useDeferredValue 的区别

  • useTransition 用于控制过渡状态,可以在过渡状态中执行任务,并提供过渡状态的布尔值来判断是否处于过渡状态。
  • useDeferredValue 用于延迟某个值的更新,以避免在渲染过程中处理昂贵的计算或数据获取,确保界面的流畅性。
  • 虽然它们都与并发模式相关,但用途和作用略有不同,具体使用哪一个需要看具体场景。

4. 应用场景

1)长列表渲染:当渲染大量列表项时,可以对列表项的渲染任务调节为低优先级异步任务,以保证用户界面的响应性能。

2)大型表单处理:对于包含大量输入字段的表单,可以使用合理使用对于hooks将表单提交和验证等任务进行优化调节,以避免阻塞用户界面。

3)图片懒加载:当页面中包含大量图片时,可以使用 useTransition 将图片的加载划分为多个低优先级异步任务,在渲染期间逐步加载图片,以减少对用户界面的阻塞。

4)异步数据加载:当页面中的数据需要从后端异步加载时,可以使用 useTransition 将数据的加载划分为多个异步任务,以保证用户界面的响应性能。

五、一个小例子

  • 以下以长列表渲染为例子做演示

基础代码,未作优化处理:

import React, { useCallback, useState } from 'react'

const index: React.FC = () => {
    const [list, setList] = useState<any[]>([])
    const handleSearch = useCallback((value: string) => {
            const newList = []
            for (let i = 0; i < 5000; i++) {
                newList.push(value + '-' + i)
            }
            setList(newList)
    }, [])

    return (
        <>
            <input onChange={(e) => handleSearch(e.target.value)} type='text' />
            <div>
                {list.map(item => <div key={item}>数据项:{item}</div>)}
            </div>
        </>
    )
}

export default index

当我们进行持续的输入时是十分的卡顿的,效果如下:
在这里插入图片描述

1. 下面使用 useTransition 进行优化

  • 降低 “setList(newList)” 的优先级,使其不阻塞用户输入事件的触发

代码修改如下:

import React, { useCallback, useState, useTransition } from 'react'

const index: React.FC = () => {
    const [list, setList] = useState<any[]>([])
    const [isPending, startTransition] = useTransition()
    const handleSearch = useCallback((value: string) => {
        startTransition(() => {
            const newList = []
            for (let i = 0; i < 5000; i++) {
                newList.push(value + '-' + i)
            }
            setList(newList)
        })
    }, [])

    return (
        <>
            <input onChange={(e) => handleSearch(e.target.value)} type='text' />
            <div>
                {isPending? '加载中。。。' : list.map(item => <div key={item}>数据项:{item}</div>)}
            </div>
        </>
    )
}

export default index

优化后效果如下:
在这里插入图片描述

2. 使用 useDeferredValue 进行优化

  • 降低 “列表部分UI” 更新渲染的优先级,使其不阻塞用户输入事件的触发

代码修改如下:

import React, { useCallback, useDeferredValue, useMemo, useState, useTransition } from 'react'

const List = ({ listData }: { listData: string[] }) => {
    return (
        <>
            {listData.map(item => <div key={item}>数据项:{item}</div>)}
        </>
    )
}

const index: React.FC = () => {
    const [list, setList] = useState<any[]>([])
    const deferredList = useDeferredValue(list)
    const handleSearch = useCallback((value: string) => {
        const newList = []
        for (let i = 0; i < 5000; i++) {
            newList.push(value + '-' + i)
        }
        setList(newList)
    }, [])

    return (
        <>
            <input onChange={(e) => handleSearch(e.target.value)} type='text' />
            <List listData={deferredList} />
        </>
    )
}

export default index

优化后效果如下:
在这里插入图片描述

补充:为什么VUE不需要设计 Concurrent Mode

  • 出于vue响应式系统的设计实现思路的不同,也就体现了为什么。

1)在vue中,响应式系统通过 proxy 实现对 render函数 的依赖收集和触发更新,基于追踪组件依赖的响应式数据的变化,可以更为精准的实现组件的更新,大大避免了不必要的渲染和更新操作,规避了react中状态更新就会触发组件及该组件下所有子组件无脑更新的问题。

2)同时vue的异步更新策略也有助于提高性能和响应能力。Vue会在下一个事件循环周期中批量更新组件,这样可以避免频繁的DOM操作和重复渲染,提高渲染效率。

3)但vue中暂时没有 useTransition 和 useDeferredValue 类似的功能操作,无法调度控制作业的优先级


提示:文章到此结束,文章为个人学习记录,侵删。

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

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

相关文章

KBP210-ASEMI大功率LED驱动器桥堆KBP210

编辑&#xff1a;ll KBP210-ASEMI大功率LED驱动器桥堆KBP210 型号&#xff1a;KBP210 品牌&#xff1a;ASEMI 封装&#xff1a;KBP-4 恢复时间&#xff1a;≥200n0s 正向电流&#xff1a;2A 反向耐压&#xff1a;1000V 芯片个数&#xff1a;4 引脚数量&#xff1a;4 …

RocketMQ5.0消息消费<一> _ PUSH模式的消息拉取

RocketMQ5.0消息消费&#xff1c;一&#xff1e; _ PUSH模式的消息拉取 一、消息消费概述 消息消费以组的模式开展&#xff0c;一个消费组内有多个消费者&#xff0c;每一个消费者可订阅多个主题&#xff0c;消费组之间有两种消费模式&#xff1a;集群模式&#xff08;默认&a…

文字识别(OCR)介绍与开源方案对比

目录 文字识别&#xff08;OCR&#xff09;介绍与开源方案对比 一、OCR是什么 二、OCR基本原理说明 三、OCR基本实现流程 四、OCR开源项目调研 1、tesseract 2、PaddleOC 3、EasyOCR 4、chineseocr 5、chineseocr_lite 6、cnocr 7、商业付费OCR 1&#xff09;腾讯…

vue+Element 设置头部固定,并解决遮罩层显示问题

通过整体框架代码可以看到&#xff0c;其实element-ui已经实现了头部固定 找到这个fixedHeader&#xff0c;发现直接在全局设置文件里 这里如果设置为false&#xff0c;就表示头部不固定&#xff1b;改为true&#xff0c;则表示头部固定。 上述更改完后&#xff0c;就可以实…

关于索引应用的一些问题

索引是啥:加快检索速度的数据结构 索引的优点和缺点 索引的优点: 1.建立索引后,数据库检索数据速度直线上升(使用正确的话),数据量越大越明显 2.分组和排序的时候,可以利用索引加快速度 3.通过建立唯一索引可以确保数据唯一,不需要加其他限制条件(既建立了索引 又保证了唯…

火山引擎 DataLeap 套件下构建数据目录(Data Catalog)系统的实践

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 摘要 Data Catalog 产品&#xff0c;通过汇总技术和业务元数据&#xff0c;解决大数据生产者组织梳理数据、数据消费者找数和理解数的业务场景&#xff0c;并服务于…

【综述003】面向未来的语义通信:基本原理与实现方法

摘要 0.引言 张平&#xff1a;提出“智简&#xff08;Intellicise&#xff09;”理念&#xff0c;提出模型驱动的语义通信框架&#xff0c;实现通信系统由传统传输比特演进为传输“模型”。如&#xff1a;语义基&#xff08;Seb&#xff09;牛凯&#xff1a;研究了从经典通信…

Three.js 三维模型(一)

简介 今天主要给搭建介绍下three.js的基本使用&#xff0c;本篇是基于笔者在16年给做的一个项目的demo版进行讲解的&#xff0c;笔者当时采用Html5和JS进行编写的。可能大家会问有没有vue、React 、angular版的。这些笔者后面有时间的时候一定会给大家介绍。 其实编程的本源在…

牌桌玩家越来越少?国产替代进程加速,中国企业要做好选择

现在“国产替代”这四个字热度很高。 可以说我们现在关注的问题&#xff0c;技术引进、自主创新、中国制造、卡脖子、全球竞争等&#xff0c;都可以用国产替代作为线索串联起来&#xff01; 其实这也不是21世纪之后中国刚刚遇到的问题。这是过去一百年中国人一直在奋斗的目标…

机器人动力学与控制学习笔记(十七)——基于名义模型的机器人滑模控制

十七、滑模控制器设计原理 滑模运动包括趋近运动和滑模运动两个过程。系统从任意初始状态趋向切换面&#xff0c;直到到达切换面的运动称为趋近运动&#xff0c;即趋近运动为的过程。根据滑模变结构原理&#xff0c;滑模可达性条件仅保证由状态空间任意位置运动点在有限时间内到…

NLP领域再创佳绩!阿里云机器学习平台 PAI 多篇论文入选 ACL 2023

近期&#xff0c;阿里云机器学习平台PAI主导的多篇论文在ACL 2023 Industry Track上入选。ACL是人工智能自然语言处理领域的顶级国际会议&#xff0c;聚焦于自然语言处理技术在各个应用场景的学术研究。该会议曾推动了预训练语言模型、文本挖掘、对话系统、机器翻译等自然语言处…

Trimble RealWorks处理点云数据(九)之点云分类后将地面导入Arcgis生成DEM

效果 步骤 1、las导入Trimble RealWorks 2、对点云数据预处理 可以参考这篇文章 TrimbleRealWorks点云数据预处理 我这边是把点云做了分类,而后将地面数据导出las 点云做为三维数据,后续步骤在arcscene中操作,能实时显示出来 3、arcscene创建las数据集

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 10 日论文合集)

文章目录 一、分割|语义相关(6篇)1.1 Unsupervised Segmentation of Fetal Brain MRI using Deep Learning Cascaded Registration1.2 Tranfer Learning of Semantic Segmentation Methods for Identifying Buried Archaeological Structures on LiDAR Data1.3 To pretrain or …

Vue2----Uniapp自定义弹窗

对于擅长后端的程序员&#xff0c;在编写前端时常常回去找库&#xff0c;比如elementUI&#xff0c;uview之类&#xff0c;但是往往这些库较为冗杂&#xff0c;有些功能比较强大&#xff0c;基本用不到&#xff0c;不好理解。这时候&#xff0c;如果可以自定义组件可能会对开发…

C++ STL常见算法

目录 1 各种常见算法的用法 1.1 非可变序列算法 1.2 可变序列算法 1.3 Partitions 1.4 排序算法 1.5 查找算法 1.6 集合算法 1.7 堆算法 1.8 最大最小值算法 1.9 其他算法 1 各种常见算法的用法 STL算法部分主要由头文件<algorithm>,<numeric>,<func…

uniapp 获取状态栏及小程序右侧胶囊信息(用于设置全屏小程序)

1.获取信息: //获取状态栏高度(px) this.statusBarHeight uni.getSystemInfoSync().statusBarHeight; //获取小程序胶囊信息 this.menuButtonInfo uni.getMenuButtonBoundingClientRect() 如下: 2.动态设置style样式: <view:style"{ paddingTop: menuButtonIn…

Oracle-RAC集群安装root.sh报错问题

问题背景: 在redhat 7.8上安装Oracle11G RAC集群&#xff0c;在节点一执行root.sh脚本时发生错误Disk Group OCRDG creation failed with the following message:ORA-15018: diskgroup cannot be created 问题分析: 从报错信息来看错误是在执行创建OCRDG磁盘组时失败&#xff0…

Python读取指定的TXT文本文件并从中提取指定数据的方法

本文介绍基于Python语言&#xff0c;遍历文件夹并从中找到文件名称符合我们需求的多个.txt格式文本文件&#xff0c;并从上述每一个文本文件中&#xff0c;找到我们需要的指定数据&#xff0c;最后得到所有文本文件中我们需要的数据的合集的方法。 首先&#xff0c;我们来明确一…

进度网络图详解

关键路径&#xff1a;总工期最长的那一条路径&#xff1a;可能不止一条。&#xff08;1条或多条&#xff09; 虚工作&#xff1a;不占用任何时间和资源的&#xff0c;只是为了让逻辑关系更加明确&#xff0c;网络图更加美观。 最早开始时间&#xff08;ES&#xff09;- 左上 最…

BT 种子,磁力链接是个啥?

[科普向] BT 种子、磁力链接到底是什么&#xff1f; BitTorrent 我们平时所说的 BT 种子&#xff0c;实际上指的是由 BitTorrent 协议所生成的一个包含资源信息的文件。与传统的网络传输协议不同&#xff0c;BitTorrent 协议是一种以 Peer-To-Peer&#xff08;P2P&#xff09…