React18 新特性前瞻

目录

1. 前言

2. Automatic batching 自动批处理

3. Suspense & SuspenseList

4. useDeferredValue

5. startTransition & useTransition

6. startTransition  与 useDeferredValue  的区别 

7. useId

8. React官网更新日志


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 (
            
{this.state.count}
) } } console.log(React.version) // concurrent // ReactDOM.createRoot(document.getElementById('root')).render() // Legacy ReactDOM.render(,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 (
            
{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的次数。

3. Suspense & SuspenseList


    Loading Points}>
        
    

    Loading Users}>
       
    
  • SuspenseList : 用于包裹Suspense组件,控制显示顺序

  • revealOrder:用于控制显示顺序

  1. together :所有suspense一起显示,即所有组件加载完成后一起显示

  2. forwards:按照组件顺序显示

  3. backwrads:反序显示

  • tail:是否显示fallback,只有在 revealOrder 为 forwards 或者 backwards时有效
  1. hidden :不显示
  2. 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 (
        
{ setText(e.target.value) }} />

{deferredText}

    { arr.map((item)=>(
  • {deferredText}
  • )) }
) } 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 (
        
{ handleValueChange(e); }} />

{content}



    { arr.map((item)=>(
  • {content}
  • )) }
) } 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官网更新日志

 

你可能感兴趣的:(React,JavaScript,javascript,前端,react.js)