目录
1. 前言
2. Automatic batching 自动批处理
3. Suspense & SuspenseList
4. useDeferredValue
5. startTransition & useTransition
6. startTransition 与 useDeferredValue 的区别
7. useId
8. React官网更新日志
我们知道,React17版本其实是18版本的一个垫脚石,有好多功能其实是React18版本的一个过渡,而React18将会带来一些真正有意义的大更新。
目前,React18已经正式于3月29日发布,脚手架已经更新到5.0.1, 而且创建的项目默认也是V18,如果需要路由也会默认安装router V6,由于一直没有关注,所以最近才发现(以下示例代码还并非运行在cli 5.0.1创建的项目)。
由下图可以看见 react-cli 目前创建的项目是 v18版本的。
React会将多次setState合并在一起进行统一渲染,但是在concurrent之前,即legacy模式下,并非全部进行批处理,在setTimeout,setInterval以及原生事件中并没有实现自动批处理,但是React18改进了这一部分,可以总结为以下几点:
有关setState在 legacy 和 concurrent 模式下的不同表现,可以查看这篇文章 React setState源码解析 - 前置知识_Monkey_Kcode的博客-CSDN博客_react setstate源码setState 在 Legacy 与 concurrent 模式下的不同表现https://blog.csdn.net/weixin_47431743/article/details/121733306接下来,我们用同样代码对比下 legacy 和 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 (
{this.state.count}
)
}
}
console.log(React.version)
// concurrent
// ReactDOM.createRoot(document.getElementById('root')).render( )
// Legacy
ReactDOM.render( ,document.getElementById('root'))
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 (
{this.state.count}
)
}
}
console.log(React.version)
// concurrent
ReactDOM.createRoot(document.getElementById('root')).render( )
// Legacy
// ReactDOM.render( ,document.getElementById('root'))
可以看到,legacy模式下会 render 两次,并且count最终的值是2;而concurrent模式下只会render一次,并且count最终的值是1;这里可以看出concurrent模式进行了自动批处理,而自动批处理会减少re-render的次数。
Loading Points}>
Loading Users}>
SuspenseList : 用于包裹Suspense组件,控制显示顺序
revealOrder:用于控制显示顺序
together :所有suspense一起显示,即所有组件加载完成后一起显示
forwards:按照组件顺序显示
backwrads:反序显示
useDeferredValue 也是 concurrent 的一部分,可以延迟更新某个不重要的部分, 实现任务的优先级处理,类似防抖节流。
言归正传,我们同样用一个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 (
{ setText(e.target.value) }} />
{deferredText}
{
arr.map((item)=>(
- {deferredText}
))
}
)
}
export default UseDeferredComponent;
如果将以上例子用 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 (
{ handleValueChange(e); }} />
{content}
{
arr.map((item)=>(
- {content}
))
}
)
}
export default UseTransitionComponent;
React 将更新分为两种,urgent update 与 transition update,而 startTransition 与 useDeferredValue都可以实现 非urgent update 的延迟更新
React新增了另外一个hooks是 useId,这个hooks可以生成一个唯一标识,具体作用要涉及到 SSR,有兴趣的可以自行了解,至于他的用法也很简单。
const id = useId()