为何React离不开useTransition和useDef...

前有科技后进阶 2025-01-18 04:30:44

大家好,很高兴又见面了,我是"高级前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

前言

Hooks 是在 React v16.8 版本中引入的概念。通过 Hooks,React 将函数组件的功能升级了,原来只能在类组件中使用的 State、Context 等技术都可以在函数组件中使用。

相比于类组件,函数组件更好理解,比如:类组件中的 this 、事件绑定等都比较棘手,而使用了 React Hooks 之后都迎刃而解。

React v18 又提供了两个新的 Hooks,useTransition() 和 useDeferredValue() 来帮助开发者确定客户端 UI 更新的优先级。

此行为确保任何繁重的 UI 更新都能顺利进行,而不太重要的 UI 更新可以并行完成或在较高优先级的更新完成后完成。 本文将探讨如何在下一个项目中使用 useTransition() 和 useDeferredValue() Hook。

为什么要优先考虑 UI 更新

优先考虑 UI 更新是优化 React 应用程序性能的一种方法。 在处理复杂的状态更新时,可能会遇到由于客户端执行的密集计算而导致某些 UI 更新缓慢的情况。

例如,假设在屏幕上渲染了 10,000 个产品的列表,并且想要实现基于产品名称的搜索功能。

理想情况下,开发者不应该一次渲染 10,000 个项目,而应使用某种分页或延迟加载技术。 但就本示例而言,假设所有项目都同时呈现在屏幕上。

现在,当实现搜索功能并将其绑定到 onChange 事件时,在输入框中输入的文本本身会非常滞后,这是因为每次按键都会更新和呈现列表中的大量产品。

这是一个完美的用例,可以优先考虑按键 UI 更新而不是下面列表的渲染。 虽然希望确保在文本框中输入内容时没有延迟,但就用户体验而言,渲染列表时出现几微秒的延迟是可以容忍的。

useTransition() Hook 的作用

React v18 通过一个名为 useTransition 的 Hook 解决了上面示例中的问题。 开发者可以简单地使用此 Hook 来包装文本框 onChange 事件。

useTransition Hook 返回一个包含两个变量的数组:

const [isPending, startTransition] = useTransition();第一个变量是一个布尔值,告诉程序非阻塞 UI 更新是否正在挂起。第二个变量是一个函数,可以包装“transition”的状态更新。表明当前转换具有更高优先级,并且作为非阻塞 UI 状态更新。

在上面的示例中,有一个 input 框和一个附加到它的 onChange 事件处理程序:

function App() { const [search, setSearch] = useState(''); function handleFilterChange(e) { setSearch(e.target.value); } return <input type="search" onChange={handleFilterChange} />;}

下面代码使用 useTransition Hook 来改善输入缓慢的性能情况,如下所示:

function handleFilterChange(e) { startTransition(() => { setSearch(e.target.value); });}

此时,setSearch(e.target.value) UI 更新将被视为过渡(transition)。

同时,开发者始终可以使用 useTransition Hook 提供的第一个变量来检查转换是否处于挂起状态,isPending 变量可用于显示主应用程序组件中的待处理转换。

以下是使用 useTransition Hook 以改善用户在输入框中输入内容时体验的完整代码:

import { useState, useTransition } from 'react';import List from './List';/* * 创建一个10000元素的数组,模拟大数据量 */function dummyList() { const items = []; for (let i = 0; i < 10000; i++) { items.push(`Item ${i + 1}`); } return items;}const list = dummyList();function filterItems(search) { if (!search) { return list; } return list.filter((product) => product.includes(filterTerm));}/* * 创建一个“List”功能组件,稍后将用于映射“App”组件下的项目列表。 */function List({ items }) { return ( <div> {items.map((item, index) => ( <Fragment key={index}> <div>{item}</div> </Fragment> ))} </div> );}function App() { const [isPending, startTransition] = useTransition(); const [search, setSearch] = useState(''); const searchedItems = filterItems(search); function handleFilterChange(e) { startTransition(() => { // 使用startTransition包裹setSearch方法 setSearch(e.target.value); }); } return ( <div> <input type="search" onChange={handleFilterChange} /> {/*`isPending` 布尔值可用于跟踪您的转换状态*/} {isPending ? <div>Loading...</div> : null} {/*渲染项目列表并传递包含过滤后的项目的 prop*/} <List items={searchedItems} /> </div> );}

下面是程序的渲染效果:

什么时候使用 useTransition()

需要注意的是 input 组件是不受控组件。 这在本例中是有效的,因为不希望状态值与 input 值同步,只是将 setSearch(e.target.value) 作为更高优先级事件进行处理。

也就是说, useTransition 无法与受控 input 组件一起使用,因为 input 的输入值和过滤结果都是同步的。 从而导致相同的问题,即由于大量列表项而导致行为缓慢。

function App() { const [search, setSearch] = useState(''); function handleFilterChange(e) { startTransition(() => { setSearch(e.target.value); }); } return ( <div> {/*❌这里永远不要使用受控组件*/} <input type="search" value={search} onChange={handleFilterChange} /> </div> );}

因此,虽然 useTransition Hook 非常适合处理状态更新,但它应该是最后的手段或只有在 UI 更新缓慢时才应使用的技巧, 在处理 CPU 速度较慢的旧设备时尤其如此。

如果操作正确,大多数 UI 更新都可以使用 React 本身来处理。 React v18 中引入了另一个名为 useDeferredValue 的 Hook,它解决了一组非常相似的问题,如果开发者无法控制状态调用,则可以使用它,如下例所示:

<List items={searchedItems} />)使用 useDeferredValue() Hook 推迟 state 更新

useDeferedValue Hooks 是 React Hooks 系列的新成员,在 React 18 的官方非测试版本中引入,该 Hooks 旨在帮助渲染缓慢的内容并避免不必要的重新渲染。

useDeferedValue Hooks 使处理状态值变得更容易、更高效。 允许开发者维护值的延迟版本,其更新频率低于实际值。 这在处理频繁更改的输入值时特别有用,因为允许 UI 保持响应的同时最大限度地减少重新渲染的次数。

在上面的示例中,如果无法控制列表状态(list state)的调用方式,而只能操作 List 组件,则也可以使用此 Hook。 在这种情况下,可以将 items 属性传递给 useDeferredValue Hook 并映射到它而非直接映射到 items,如下所示:

function List({ items }) { const deferredItems = useDeferredValue(items); return ( <div> {deferredItems.map((item, index) => ( <Fragment key={index}> <div>{item}</div> </Fragment> ))} </div> );}

上面代码将确保 input 更新快速,而列表需要一段时间才能更新。 如果在搜索功能中使用了去抖功能,可能已经看到过这种行为。此 Hook 的行为与此完全相同,但会延迟每次输入时的列表 UI 更新。

一般来说,如果可以控制状态调用,那么使用 useTransition Hook 更好。 否则,应该始终延迟 List 组件本身的 UI 更新,有点类似于 debounce 方法。

值得注意的是,永远不要将 useDeferredValue 和 useTransition Hook 混合搭配,因为都是为了解决相同的问题。

但是,可以将 useDeferredValue 与 debounce 或 throttle 一起使用,从而进一步改善用户体验并在用户与输入框交互时节省一些网络调用。

参考资料

https://blog.logrocket.com/understanding-prioritize-react-ui-updates/

https://www.dhiwise.com/post/supercharge-your-react-app-with-the-usedeferedvalue-hook

https://blog.openreplay.com/usetransition-vs-usedeferredvalue-in-react-18/

0 阅读:0
前有科技后进阶

前有科技后进阶

感谢大家的关注