【React Hooks原理 - useTransition】

概述

在上一篇中我们介绍了useDeferredValue的基本原理,本文主要介绍一下useTransition这个Hook,之所以在这里提到useDeferredValue,是因为这两个Hook都是在React18引入的进行渲染优化的Hooks,在某些功能上是重叠的,主要区别如下:useTransition是在useDeferredValue之前运行,主要是对状态更新更新延迟,即降低setValue更新状态请求的优先级,让其延后更新,来减少不必要的渲染。而useDeferredValue则是通过绑定state来返回一个旧值来延迟新组件的不必要渲染,两者都是降低更新的优先级,只是针对的对象不一致。

基本使用

老规矩,我们还是先定义入手来看看useTransition是如何使用的。

const [isPending, startTransition] = useTransition()

该Hook不接收参数,会返回一个包含两个函数的数组[isPending,startTransition ]

  • isPending:布尔值,告诉你是否正在处理过渡更新,一般可以根据该值添加loading等效果。
  • startTransition :是一个调用一个或多个set 函数更新状态的函数,使用此方法降低状态更新的优先级,进行延迟更新,更新标记为 transition。
import React, { useState, useTransition } from 'react';function App() {const [isPending, startTransition] = useTransition();const [inputValue, setInputValue] = useState('');const [computedValue, setComputedValue] = useState('');const handleChange = (e) => {const value = e.target.value;setInputValue(value);// 使用 startTransition 将计算标记为低优先级startTransition(() => {const newValue = computeExpensiveValue(value);setComputedValue(newValue);});};return (<div><input type="text" value={inputValue} onChange={handleChange} />{isPending ? <p>Loading...</p> : <p>Computed Value: {computedValue}</p>}</div>);
}// 模拟一个耗时计算函数
function computeExpensiveValue(input) {let result = '';for (let i = 0; i < 100000; i++) {result = input;}return result;
}export default App;

可能有的人看到这个会有些疑惑,看着写法感觉和useDeferredValue有点类似呀,在用户持续输入耗时任务时起到减少渲染的作用。其实没错,这就是上面说的这两个hook的重叠地方,都是通过降低优先级来处理的,内部一些处理逻辑是一致的,而主要区别在于useDeferredValue是对一个状态值的优化,订阅一个状态值并返回一个旧值,此时已经发起了状态值的更新,组件的重新渲染,只不过被React给挂起了。而useTransition则是直接对更新函数进行操作,不管一个还是多次状态更新主要被其包裹都会推迟更新,此时都不会发起状态更新的请求。

源码解析

Mount阶段

在Mount阶段,主要通过mountTransition来调用startTransition来控制状态的更新。代码如下:由于


function mountTransition(): [boolean,(callback: () => void, options?: StartTransitionOptions) => void
] {const stateHook = mountStateImpl((false: Thenable<boolean> | boolean));// The `start` method never changes.const start = startTransition.bind(null,currentlyRenderingFiber,stateHook.queue,true,false);const hook = mountWorkInProgressHook();hook.memoizedState = start;return [false, start];
}

从代码能看出mountTransition主要是以下逻辑:

  • 调用mountStateImpl函数来初始化内部状态,即暴露的isPending,用于判断当前是否在执行过渡更新
  • 通过startTransition来管理状态更新,其中会降低优先级和维护isPending状态
  • 通过mountWorkInProgressHook来创建一个新的hook,用于fiber节点对useTransition这个Hook的追踪和管理
  • 返回[isPending, startTransition]数组

可能这里会有疑问,为什么要创建两个Hook:stateHookhook
这是因为hook用于组件fiber来管理useTransition这个Hook的,其中fiber.memonizedState指向的就是useTransition。而stateHook用于管理我们通过startTransition触发的过渡更新,其中通过dispatchSetState维护了isPending的状态,以及降低其优先级为过渡优先级。

// 同步优先级 最高
export const SyncLane = 0b00001;
// 输入框等交互优先级
export const InputContinuousLane = 0b00010;
// 默认优先级
export const DefaultLane = 0b00100;
// useTransition优先级
export const TransitionLane = 0b01000;
// 空闲
export const IdleLane = 0b10000;

如上所示,过渡优先级仅比空闲优先级高,所以将状态更新设置为过渡优先级之后,会优先执行其他任务,最后再执行过渡更新。

下面我们逐一介绍mountStateImplstartTransition这两个函数。

mountStateImpl函数

function mountStateImpl<S>(initialState: (() => S) | S): Hook {const hook = mountWorkInProgressHook();hook.memoizedState = hook.baseState = initialState;const queue: UpdateQueue<S, BasicStateAction<S>> = {pending: null,lanes: NoLanes,dispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: (initialState: any),};hook.queue = queue;return hook;
}

该函数主要就是创建一个Hook并绑定一个初始值initialState,然后根据这个状态值生产一个更新对象queue,最后返回该hook。 此处的initialState就是返回的isPending,默认为false。

当我们通过startTransition来触发过渡更新时,会调用该函数:
其中enableAsyncActions表示是否开启异步操作,较新的React版本会默认开启。为了方便阅读,将该函数拆分为了开启异步和不开启异步两部分来介绍。

开启异步:

function startTransition<S>(fiber: Fiber,queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,pendingState: S,finishedState: S,callback: () => mixed,options?: StartTransitionOptions
): void {const previousPriority = getCurrentUpdatePriority();setCurrentUpdatePriority(higherEventPriority(previousPriority, ContinuousEventPriority));const prevTransition = ReactSharedInternals.T;const currentTransition: BatchConfigTransition = {};ReactSharedInternals.T = currentTransition;dispatchOptimisticSetState(fiber, false, queue, pendingState);try {const returnValue = callback();const onStartTransitionFinish = ReactSharedInternals.S;if (onStartTransitionFinish !== null) {onStartTransitionFinish(currentTransition, returnValue);}if (returnValue !== null &&typeof returnValue === "object" &&typeof returnValue.then === "function") {const thenable = ((returnValue: any): Thenable<mixed>);const thenableForFinishedState = chainThenableValue(thenable,finishedState);dispatchSetState(fiber, queue, (thenableForFinishedState: any));} else {dispatchSetState(fiber, queue, finishedState);}} finally {setCurrentUpdatePriority(previousPriority);ReactSharedInternals.T = prevTransition;}
}
  1. 第一步是通过getCurrentUpdatePriority来获取当前更新任务的优先级,比如持续Input输入则当前优先级为InputContinuousLane

  2. 通过setCurrentUpdatePriority来提高当前任务的优先级higherEventPriority(previousPriority, ContinuousEventPriority),这一步提高优先级是为了保证返回的isPending状态及时更新,不影响用户交互,比如显示loading加载提示,此时还没有进入过渡更新。

  3. ReactSharedInternals保存共享的初始状态,有React内部维护,此处是获取过渡更新状态。

  4. 调用dispatchOptimisticSetState来更新isPending将值设置为true并调用scheduleUpdateOnFiber触发fiber更新

  5. 进入try catch逻辑,这里面就是处理过渡更新,并降低优先级的逻辑。会先执行startTransition包裹的状态更新,并得到返回值returnValue

  6. 判断返回的值是否是Promise对象

    • 如果是,则通过chainThenableValue来等待Promise执行完成并设置其完成状态。然后调用dispatchSetState来降低优先级并等待执行
    • 如果不是则直接调用dispatchSetState来降低优先级并等待执行,在该函数中会调用requestUpdateLane来获取当前优先级,如果是过渡任务,则会返回过渡优先级。
  7. 最后执行setCurrentUpdatePriority恢复之前任务本身的优先级,并更新共享数据ReactSharedInternals的值

Update阶段

主要流程在Mount阶段已经梳理了,在这里看看在Update时内部是怎样运动的。

function updateTransition(): [boolean,(callback: () => void, options?: StartTransitionOptions) => void,
] {const [booleanOrThenable] = updateState(false);const hook = updateWorkInProgressHook();const start = hook.memoizedState;const isPending =typeof booleanOrThenable === 'boolean'? booleanOrThenable: // This will suspend until the async action scope has finished.useThenable(booleanOrThenable);return [isPending, start];
}

从代码能看出,主要就是返回最新的isPenging的值,并创建一个更新任务添加到Hook中。

总结

useTransition主要是针对状态更新函数set函数,降低其优先级为过渡优先级,在调度器中延后执行,以达到延迟更新的目的,当用户持续输入时减少渲染。其中主要两点:

  • 内部维护isPending状态,同暴露到应用层,方便进行交互优化(loading提示等)
  • 降低更新优先级为过渡优先级,延迟状态的更新

题外话 - 号外

本文也是根据这些文章学习进行梳理在自己理解的基础上书写的,如有问题,还请指正。

有兴趣的朋友可以关注一下公众号,方便随时随地一起交流学习。
在这里插入图片描述

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

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

相关文章

面试面到自闭,字节软件测试岗五轮面试,四个小时灵魂拷问...

准备过程 我自己是本科毕业后在老东家干了两年多&#xff0c;老东家算是一家”小公司”(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身)&#xff0c;毕业这两年多我也没有在大厂待过&#xff0c;因此找坑的时候是非常非常虚的。迫于心慌&#xff0c;我好好思考了一阵来…

<设计模式> 工厂模式

工厂模式 模式介绍&#xff1a;将对象的创建过程封装在工厂类中&#xff0c;客户端代码只需要关心从工厂获取对象的过程&#xff0c;而不需要了解对象的创建细节。这样做的好处包括提高了代码的灵活性和可扩展性&#xff0c;降低了对象之间的耦合度。主要解决接口选择的问题。…

Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

目录 1&#xff09;内存相关的五种常见问题 2&#xff09;内存溢出和内存泄漏 3&#xff09;LeakCanary是什么? 4&#xff09;LeakCanary如何使用&#xff0c;如何分析&#xff1f; 5&#xff09;LeakCanary监测的内容 提问&#xff1a;程序有时候很卡&#xff0c;经常会出现…

前端开发:Vue2.0桌面组件库-Element

引入Element的步骤&#xff1a; 1.在vscode终端中执行命令&#xff08;需要联网&#xff09; 下载成功 2.在main.js中导入element.ui组件库。 同上&#xff0c;自定义的组件需要先在根组件中引入。 3.访问官网&#xff0c;复制调整代码

变阻器的主要特性和参数有哪些?

变阻器的主要特性和参数有很多&#xff0c;下面将详细介绍几个重要的特性和参数&#xff1a; 1. 电阻范围&#xff1a;滑动变阻器的电阻范围是指其最大电阻值和最小电阻值之间的范围&#xff0c;这个范围通常由制造商指定&#xff0c;用户在选择变阻器时需要根据实际需求选择合…

基于 SSM 的汽车租赁系统

基于 SSM 的电器网上订购系统 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Spring、JSP、MyBatis 工具&#xff1a;MyEclipse/IDEA、Tomcat 引言 汽车租赁是在约定时间内&#xff0c;租赁经营人将租赁汽车&#xff08;包括载货汽车和载客汽车&#x…

【机器学习】机器学习的详细阐述

机器学习&#xff08;Machine Learning, ML&#xff09;是一种通过从数据中学习来自适应改进预测和决策的人工智能技术。以下是对机器学习的详细阐述&#xff1a; 一、机器学习的定义 机器学习主要研究计算机系统对于特定任务的性能&#xff0c;逐步进行改善的算法和统计模型…

AFSim 仿真系统--子系统几何考虑

子系统几何考虑 概述 由于WSF试图表示以多种方式运行的子系统&#xff08;传感器&#xff0c;武器或通信&#xff09;&#xff0c;因此它提供的定义属性的机制&#xff0c;如几何限制&#xff0c;可能相当令人生畏。本文档提供了关于这些机制如何运作以及如何定义行为类似于真实…

Axure RP:打造动态交互的大屏可视化设计利器

Axure大屏可视化是指使用Axure RP这款原型设计工具来创建具有视觉冲击力和数据展示功能的大屏幕界面。Axure以其强大的交互设计和丰富的组件库&#xff0c;成为了实现大屏可视化的重要工具之一。以下是对Axure大屏可视化的详细阐述&#xff1a; 一、Axure在大屏可视化中的优势 …

给视频配背景音乐

介绍 给一段视频&#xff0c;想配个背景音乐&#xff0c;常规办法就是打开抖音或者剪映&#xff0c;咔咔咔一顿操作&#xff0c;完事儿&#xff0c;简单方便。但是如果你作为一个开发者呢&#xff0c;你又不能直接调用抖音剪映的接口吧&#xff08;特别有钱定制除外&#xff0…

ctfshow web入门 中期测评 web503--web516(无web511--web514)

web503 看了之前的文件的发现都没办法利用了 这个页面的源码发现了 layui.use([layer, form], function(){var layer layui.layer,form layui.form;form.on(submit(admin_settings), function(data){$.ajax({url:api/admin_settings.php,dataType:"json",type:po…

新手必看:Elasticsearch 入门全指南

Elasticsearch 入门介绍 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;广泛应用于处理大规模数据和实时搜索需求。它基于 Apache Lucene 构建&#xff0c;具备高可扩展性和分布式特性&#xff0c;能够快速、可靠地存储、搜索和分析大量数据。本文将介绍 Elasti…

STM32项目分享:智能台灯(机智云)系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.com/video/BV1My411q7fE…

CheckBox实现原理分析

CheckBox 是 Android 中的一个常用控件&#xff0c;用于实现复选框的功能。它继承自 CompoundButton&#xff0c;后者又继承自 Button。CheckBox 可以用来表示一个布尔值的选择状态&#xff0c;通常用于收集用户的选择&#xff0c;例如在表单中选择多个选项。 接下来&#xff…

小白学大模型:LLaMA-Factory 介绍与使用

最近这一两周看到不少互联网公司都已经开始秋招提前批了。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友…

基于CentOS Stream 9平台安装MySQL Community Server 9.0.1 Innovation

1. 安装之前 1.1 查看系统版本 cat /etc/redhat-releaseCentOS Stream release 9 1.2 查看cpu架构 lscpu架构&#xff1a; x86_64 CPU 运行模式&#xff1a; 32-bit, 64-bit 2. 官网下载 https://dev.mysql.com/downloads/mysql/ 要多看看 官方9.0文档&#xff1a;https://d…

C语言——输入你的身高和体重,测试你的健康状况。

输入你的身高和体重&#xff0c;测试你的健康状况。 计算bmi的值&#xff0c; bmi &#xff08;体重/身高的平方) 如果bmi 小于18.5&#xff0c;则显示“偏瘦&#xff0c;注意加强营养” 如果bmi 在18.5和23.9之间&#xff0c;则显示“体重指数良好&#xff0c;注意保持” 如果…

NSL-KDD入侵检测系统的设计与实现系列预告

每日进阶-基于机器学习的入侵检测系统——打怪升级之道 在当今的数字时代&#xff0c;网络安全不仅是防御&#xff0c;更是主动出击。你是否想知道如何用机器学习技术设计一套入侵检测系统&#xff08;IDS&#xff09;&#xff0c;让黑客无所遁形&#xff1f;本系列文章将为您揭…

unity2D游戏开发12单例

单例 我们先了解一种被称为单例的软件设计模式。当应用程序需要在生命周期内创建特定类的单个实例时,可以使用单例。当一个类提供了游戏中其他几个类使用的功能时,单例会很有用,例如,在Game Manager 类中协调游戏逻辑,单例可以提供对该类及其功能的公共统一访问入口。单例…