目录
5. startTransition & useTransition
6. startTransition 与 useDeferredValue 的区别
1. 前言
我们知道,React17版本其实是18版本的一个垫脚石,有好多功能其实是React18版本的一个过渡,而React18将会带来一些真正有意义的大更新。
目前,React18已经正式于3月29日发布,脚手架已经更新到5.0.1, 而且创建的项目默认也是V18,如果需要路由也会默认安装router V6,由于一直没有关注,所以最近才发现(以下示例代码还并非运行在cli 5.0.1创建的项目)。
由下图可以看见 react-cli 目前创建的项目是 v18版本的。
2. Automatic batching 自动批处理
React会将多次setState合并在一起进行统一渲染,但是在concurrent之前,即legacy模式下,并非全部进行批处理,在setTimeout,setInterval以及原生事件中并没有实现自动批处理,但是React18改进了这一部分,可以总结为以下几点:
- leagcy模式下,setTimeout等非react api下不会进行批处理
- leagcy模式下,可以使用 ReactDOM.unstable_batchedUpdates 将其手动转为批处理
- concurrent模式下,即使是 setTimeout 等非react api,也会进行自动批处理
- concurrent模式下,可以使用 ReactDOM.flushSync 转为非批处理
有关setState在 legacy 和 concurrent 模式下的不同表现,可以查看这篇文章 React setState源码解析 - 前置知识_Monkey_Kcode的博客-CSDN博客_react setstate源码setState 在 Legacy 与 concurrent 模式下的不同表现https://blog.csdn.net/weixin_47431743/article/details/121733306接下来,我们用同样代码对比下 legacy 和 concurrent,简单看下自动批处理的优势
- legacy模式
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component{
state = {
count:0
}
handleClick = ()=>{
// debugger
setTimeout(()=>{
this.setState({
count:this.state.count + 1
})
console.log(this.state.count)
this.setState({
count:this.state.count + 1
})
console.log(this.state.count)
},0)
}
render(){
console.log('render')
return (
<div>
{this.state.count}
<button onClick={()=>{this.handleClick()}}>click</button>
</div>
)
}
}
console.log(React.version)
// concurrent
// ReactDOM.createRoot(document.getElementById('root')).render(<App/>)
// Legacy
ReactDOM.render(<App/>,document.getElementById('root'))
- concurrent模式
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component{
state = {
count:0
}
handleClick = ()=>{
// debugger
setTimeout(()=>{
this.setState({
count:this.state.count + 1
})
console.log(this.state.count)
this.setState({
count:this.state.count + 1
})
console.log(this.state.count)
},0)
}
render(){
console.log('render')
return (
<div>
{this.state.count}
<button onClick={()=>{this.handleClick()}}>click</button>
</div>
)
}
}
console.log(React.version)
// concurrent
ReactDOM.createRoot(document.getElementById('root')).render(<App/>)
// Legacy
// ReactDOM.render(<App/>,document.getElementById('root'))
可以看到,legacy模式下会 render 两次,并且count最终的值是2;而concurrent模式下只会render一次,并且count最终的值是1;这里可以看出concurrent模式进行了自动批处理,而自动批处理会减少re-render的次数。
3. Suspense & SuspenseList
<SuspenseList revealOrder='backwards' tail='collapsed'>
<Suspense fallback={<h1>Loading Points</h1>}>
<AsyncComponent res={Points}/>
</Suspense>
<Suspense fallback={<h1>Loading Users</h1>}>
<AsyncComponent res={users}/>
</Suspense>
</SuspenseList>
-
SuspenseList : 用于包裹Suspense组件,控制显示顺序
-
revealOrder:用于控制显示顺序
-
together :所有suspense一起显示,即所有组件加载完成后一起显示
-
forwards:按照组件顺序显示
-
backwrads:反序显示
- tail:是否显示fallback,只有在 revealOrder 为 forwards 或者 backwards时有效
- hidden :不显示
- collapsed:轮到自己再显示
4. useDeferredValue
useDeferredValue 也是 concurrent 的一部分,可以延迟更新某个不重要的部分, 实现任务的优先级处理,类似防抖节流。
- 防抖: 以电脑休眠为例,假如我们设置电脑30秒不操作即进行休眠,如果在30秒内进行了操作,将重新计时。
- 防抖场景:我们在使用input输入框搜索内容时,输入框下方会显示与输入内容相关的所有词条,这个action往往需要去数据库查询。如果这个查询优化的不够完美,而我们每输入一个单词都需要去查询,这将造成很大的性能负担。因此,我们可以使用防抖,在输入一个字符后一小段时间后再进行联想搜索(查询相关字段),如果在这个时间内我们又输入了另一个字符,将会重新计时,因为用户可能想搜索的是 react,而不是 r 或者 re,我们可以减少 r 或者 re 的查询,以实现请求的减少。(当然,目前浏览器优化的更完美,只是以此为例)
言归正传,我们同样用一个input的例子体会下 useDeferredValue 的作用。假如输入框输入的内容关联40000条数据同时渲染,这会导致 input 输入的时候很卡,我们键入一个单词很久才看到反馈,这大大影响了用户交互。因此,我们可以将除 input 外的渲染延迟,因为input属于高优先级任务,理应得到快速响应。
那么,使用 useDeferredValue 与 使用 计时器(防抖节流)有什么区别呢,最简单的区别就是防抖节流需要等待特定时间,而 useDeferredValue 将在高优先级任务完成后立即执行其他更新。
以下代码,可以对注释位置进行操作,以对比 使用 useDeferredValue 与 不使用 useDeferredValue 的区别。
import React from "react";
import { useState , useDeferredValue} from "react"
function UseDeferredComponent() {
const [text, setText] = useState('')
// const deferredText = text;
const deferredText = useDeferredValue(text);
let arr = []
for (let i = 0; i < 40000; i++) {
arr.push(i)
}
return (
<div>
<input value={text} onChange={(e) => { setText(e.target.value) }} />
<h1>{deferredText} </h1>
<ul>
{
arr.map((item)=>(
<li key={item}>{deferredText}</li>
))
}
</ul>
</div>
)
}
export default UseDeferredComponent;
5. startTransition & useTransition
- startTransition: 执行的过渡函数(可单独引入,亦可通过 useTransition 解构)
- isPending:过渡任务状态,true为正在过渡,false为完成过渡
- useTransition:const [isPending, startTransition] = useTransition();
如果将以上例子用 startTransition 该如何实现呢?同样可以解开注释语句进行对比。
import React,{ useState, useTransition } from "react";
function UseTransitionComponent(){
const [isPending, startTransition] = useTransition();
const [text, setText] = useState('')
const [content,setContent] = useState();
let arr = []
for (let i = 0; i < 40000; i++) {
arr.push(i)
}
const handleValueChange = (e)=>{
setText(e.target.value);
startTransition(()=>{
setContent(e.target.value)
})
// setContent(e.target.value)
}
return (
<div>
<input value={text} onChange={(e) => { handleValueChange(e); }} />
<br />
<h1>{content} </h1>
<br />
<button disabled={isPending}>If status is pending, this button will be disabled</button>
<br />
<ul>
{
arr.map((item)=>(
<li key={item}>{content}</li>
))
}
</ul>
</div>
)
}
export default UseTransitionComponent;
6. startTransition 与 useDeferredValue 的区别
React 将更新分为两种,urgent update 与 transition update,而 startTransition 与 useDeferredValue都可以实现 非urgent update 的延迟更新
- startTransition 可以想象为 useCallback,是将函数中的内容进行过渡
- useDeferredValue 可以想象为 useMemo,是对值进行过渡
- startTransition 的回调函数是同步执行的,会比 useDeferredValue 执行的时机更早
- startTransition 和 useDeferredValue 与 setTimeout(防抖节流)的区别在于 不需要等待特定时间,可以监听任务的工作状态,当 urgent update 更新完毕,将会自动执行 transition update
7. useId
React新增了另外一个hooks是 useId,这个hooks可以生成一个唯一标识,具体作用要涉及到 SSR,有兴趣的可以自行了解,至于他的用法也很简单。
const id = useId()
8. React官网更新日志