【React Hooks原理 - useTransition】

news2024/11/13 13:08:22

概述

在上一篇中我们介绍了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.coloradmin.cn/o/1957197.html

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

相关文章

YOLO入门教程(一)——训练自己的模型【含教程源码 + 故障排查】

目录 引言前期准备Step1 打标训练Step2 格式转换Step3 整理训练集Step4 训练数据集4.1创建yaml文件4.2训练4.3故障排查4.3.1OpenCV版本故障&#xff0c;把OpenCV版本升级到4.0以上4.3.2NumPy版本故障&#xff0c;把NumPy降低版本到1.26.44.3.3没有安装ultralytics模块4.3.4Aria…

自闭症儿童上学指南:帮助孩子适应校园生活

在自闭症儿童成长的道路上&#xff0c;校园生活是他们融入社会、学习新知、发展社交技能的重要一步。作为星启帆自闭症儿童康复机构&#xff0c;我们深知这一过程对于孩子及其家庭而言既充满挑战也极具意义。 一、前期准备&#xff1a;建立坚实的支持体系 1. 深入了解孩子需求 …

【机器学习】梯度下降函数如何判断其收敛、学习率的调整以及特征缩放的Z-分数标准化

#引言 在机器学习中&#xff0c;特征缩放和学习率是两个非常重要的概念&#xff0c;它们对模型的性能和训练速度有显著影响。 特征缩放是指将数据集中的特征值缩放到一个固定的范围内&#xff0c;通常是在0到1之间或者标准化到均值为0、方差为1。特征缩放对于模型的训练至关重要…

Vmware安装openstack

安装虚拟机 创建完成后&#xff0c;点击开启虚拟机 稍等执行成功后 上传压缩包到指定目录。将yoga_patch.tar.gz包上传至/root目录下&#xff0c;将stack3_without_data.tar.gz包使用WinSCP上传至/opt目录下 vim run_yoga.sh #/bin/bash cd /root sudo apt-get update tar -xzv…

java面向对象总结

java面向对象篇到这里就已经结束了&#xff0c;有什么不懂的地方可以逐一进行重新观看。希望大家能够从入门到起飞。 Java面向对象基础篇综合训练&#xff08;附带全套源代码及逐语句分析&#xff09;-&#xff1e;基于javabeen Java面向对象进阶篇综合训练&#xff08;附带全…

如何跨专业通过软件设计师考试

链接&#xff1a;如何跨专业通过软件设计师-软件中级职称考试 (qq.com)

经济下行,企业还在“裁员至上”?

最近小红书、B站崩溃&#xff0c;又延伸到某云服务厂商问题频发&#xff0c;让人忍不住戏谑&#xff1a;“这算不算裁员裁到大动脉&#xff1f;” 在阿道看来&#xff0c;各大企业的裁员动作&#xff0c;绕不开的依旧是“人月神话”&#xff1a;盲目加人带来的是成本的倍增和效…

STKMATLAB 卫星编队覆盖分析纯代码实现

任务描述 设置卫星编队&#xff08;沿航迹编队&#xff0c;大斜视角&#xff0c;幅宽100km&#xff0c;下视角30&#xff0c;斜视角26&#xff09;&#xff0c;设置分析区域&#xff08;中国全境&#xff09;&#xff0c;设置FigureOfMerit&#xff08;展示覆盖率&#xff09;…

skynet 入门篇

文章目录 概述1.实现了actor模型2.实现了服务器的基础组件 环境准备centosubuntumac编译安装 ActorActor模型定义组成 Actor调度工作线程流程工作线程权重工作线程执行规则 小结 概述 skynet 是一个轻量级服务器框架&#xff0c;而不仅仅用于游戏&#xff1b; 轻量级有以下几…

16现代循环神经网络—深度循环与双向循环

目录 1.深度循环神经网络回顾:循环神经网络总结简洁代码实现2.双向循环神经网络隐马尔可夫模型中的动态规划双向模型模型的计算代价及其应用总结代码实现1.深度循环神经网络 回顾:循环神经网络 如何将循环神经网络变深,以获得更多的非线性? 通过添加多个隐藏层的方式来实现…

Transformer中的Multi-head与Self-Attention机制解析——从单一关注到多元自省的效益最大化

Transformer中的Multi-head与Self-Attention机制解析——从单一关注到多元自省的效益最大化 Multi-head Attention与Self-Attention的核心思想 想象一下&#xff0c;你在读一本书&#xff0c;你想要同时关注到书中的多个角色和情节。多头注意力机制就是让你能够同时关注到多个不…

day14:01函数参数的使用

一、形参与实参介绍 【形参】&#xff1a;在定义函数阶段定义的参数称之为形式参数&#xff0c;简称形参,相当于变量名 def func(x, y): # x1&#xff0c;y2print(x, y)【实参】&#xff1a;在调用函数阶段传入的值称之为实际参数&#xff0c;简称实参&#xff0c;相当于变量…

OpenEuler系统下——k8s离线安装部署

简介 K8S是目前已经是业界最为流行的开源技术框架&#xff0c;但是苦于其学习难度较大&#xff0c;并且初学者在开始的时候需要自己进行安装搭建部署&#xff0c;以供后续的学习使用&#xff0c;但是国内经常会出现无法访问外网的官方网站&#xff0c;导致很多镜像和依赖包无法…

uCore2020 lab1练习一作业

在线环境&#xff08;无需搭建环境即可复现&#xff09; 在线实验指导书uCore2020&#xff0c;有些章节无法访问 文章目录 lab1练习一1、操作系统镜像文件ucore.img生成过程init.o等文件的生成过程bin/kernal的生成过程bin/sign的生成过程bin/bootblock的生成过程bin/ucore.i…

搭建AI无人直播插件

在当今的数字时代&#xff0c;直播已成为一种极为流行的内容传播方式&#xff0c;而人工智能(AI)技术的飞速发展更是为直播行业注入了新的活力。 AI无人直播插件&#xff0c;作为这一趋势下的产物&#xff0c;不仅能够实现24小时不间断的直播内容生成&#xff0c;还能根据观众…

SpringCloud Alibaba 微服务(四):Sentinel

目录 前言 一、什么是Sentinel&#xff1f; Sentinel 的主要特性 Sentinel 的开源生态 二、Sentinel的核心功能 三、Sentinel 的主要优势与特性 1、丰富的流控规则 2、完善的熔断降级机制 3、实时监控和控制台 4、多数据源支持 5、扩展性强 四、Sentinel 与 Hystrix…

Redis 序列化 GenericJackson2JsonRedisSerializer和Jackson2JsonRedisSerializer的区别

GenericJackson2JsonRedisSerializer 和 Jackson2JsonRedisSerializer 是 Spring Data Redis 提供的两种基于 Jackson 的 Redis 序列化器。 它们的主要区别在于序列化和反序列化的方式以及适用的场景。 GenericJackson2JsonRedisSerializer 序列化方式&#xff1a;在序列化对…

为Mac配置Alfred

参考资料&#xff1a; Alfred神器使用手册 | louis blogMacOS神器之Alfred workflow概览GitHub - arpir/Alfred-Workflows-Collection: 一些好用的 Alfred Workflow 一、修改快捷键 Spotlight的默认快捷键是Command Space Alfred的默认快捷键是Option Space 可以将Alfred和…

[网络编程】网络编程的基础使用

系列文章目录 1、 初识网络 网络编程套接字 系列文章目录前言一、TCP和UDP协议的引入二、UDP网络编程1.Java中的UDP2.UDP回显代码案例3.UDP网络编程的注意事项 三、TCP网络编程1.TCP回显代码案例2.TCP多线程使用 总结 前言 在学习完基础的网络知识后&#xff0c;完成跨主机通…

关于 OSPF LSA 序列号范围 0x80000001-0x7FFFFFFF 释疑正本清源

注&#xff1a;机翻&#xff0c;未校对。 正本&#xff1a;RFC 2328 OSPF Version 2 中相关解释 April 1998 12.1.6. LS sequence number 12.1.6. 序列号 The sequence number field is a signed 32-bit integer. It is used to detect old and duplicate LSAs. The space …