改进已有属性,如自动批量处理【setState】、改进Suspense、组件返回undefined不再报错等
支持Concurrent模式,带来新的API,如useTransition、useDeferredValue等
如何升级React 18
npm install react@latest react-dom@latestnpm install @types/react@latest @types/react-dom@latest
react18 使用 ReactDOM.createRoot() 创建一个新的根元素进行渲染,使用该 API,会自动启用并发模式。如果你升级到react18,但没有使用ReactDOM.createRoot()
代替ReactDOM.render()
时,控制台会打印错误日志要提醒你使用React,该警告也意味此项变更没有造成breaking change,而可以并存,当然尽量是不建议
import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';interface MyModule extends NodeModule {hot: {accept: () => void;};
}
let myM: MyModule = module as MyModule;
if (myM && myM.hot) {myM.hot.accept();
}// ReactDOM.render(<App />, document.querySelector('#root'));
// ReactDOM.createRoot(document.querySelector('#root') as HTMLElement).render(<App />);const container = document.querySelector('#root');// 装载
ReactDOM.createRoot(document.querySelector('#root')).render(<React.StrictMode><App /></React.StrictMode>,
);
// 卸载
ReactDOM.createRoot(document.querySelector('#root')).unmount();
创建根以渲染或卸载的新方法
react18的更新特点
并发模式 Concurrent Mode(并发模式)
- react18的新特性是使用现代浏览器的特性构建的,彻底放弃对 IE 的支持。
- 并发模式是实现并发更新的基本前提
- react18 中,以是否使用并发特性作为是否开启并发更新的依据。
- 并发特性指开启并发模式后才能使用的特性,比如:
useDeferredValue
/useTransition
react17 和 react18 的区别就是:从同步不可中断更新变成了异步可中断更新,react17可以通过一些试验性的API开启并发模式,而react18则全面开启并发模式。
并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,该模式通过使渲染可中断来修复阻塞渲染限制。在并发模式中,React 可以同时更新多个状态。
Automatic Batching自动批量更新state,减少渲染次数
- react18之前,React Event Handler
- react18之后,React Event Handler、Promise、setTimeout、native event handler等
如何禁用(不推荐)—— 可以使用ReactDom;提供的flushSync方法
import {flushSync} from 'react-dom'function handleClick(){flushSync(()=>{setCounter(c=>c+1);})// React has updated the DOM by nowflushSync(()=>{setFlag(f=>!f);})// React has updated the DOM by now
}
react18的新的API
1. Transitions 控制渲染的优先级
新的api是useTransition和startTransition
渲染分为高优先级和低优先级,高优先级就是正常的state更新,低优先级使用transition Api 包裹
总结:
- 区别渲染优先级
- 应对同时大量渲染的情况
比如:当用户在输入框查询数据时,需要实时根据用户输入数据进行搜索提示项的展示。与以往不同的是,提示列表的个数是十分庞大的,每次都在10000条以上。
import {useState} from "react";
import styles from "./index.module.css";
const Home:React.FC = () => {const [val,setVal] = useState('');const [arr,setArr] = useState<number[]>([]);//根据用户输入获取查询列表const getList = (e:any) => {setVal(e.target.value)let arr = Array.from(new Array(10000),item=>Date.now())setArr(arr);}return (<div className={styles.box}><input value={val} onChange={getList}/>{arr.map((item,key)=>(<div key={key}>{item}</div>))}</div>)
}
export default Home;
现象:我们快速在表单输入了abcd,但是很明显出现了卡顿现象,大约2s后表单和列表数据都被渲染。
新的api:在更新大数据更新或者大列表dom时,为了页面性能和渲染优化,我们可以对大数据或列表的更新过程采用startTransition优雅降级处理。
import {useState,startTransition} from "react";
import styles from "./index.module.css";
const Home:React.FC = () => {const [val,setVal] = useState('');const [arr,setArr] = useState<number[]>([]);//根据用户输入获取查询列表const getList = (e:any) => {setVal(e.target.value)let arr = Array.from(new Array(10000),item=>Date.now());startTransition(()=>{setArr(arr);})}return (<div className={styles.box}><input value={val} onChange={getList}/>{arr.map((item,key)=>(<div key={key}>{item}</div>))}</div>)
}
export default Home;
现象:此处的输入框数据优化了许多,并且大数据列表展示卡顿达到了一定程度优化。
注意:useTransition和startTransition的功能一模一样,只是通过hooks的展现方式出现,并且增加了保存列表是否在渲染等待的状态。
第一个变量保存是否渲染中的状态,ture表示渲染等待中
第二个变量和startTransition的使用方式一模一样
import React,{useState,useTransition} from "react";
const Home:React.FC = () => {const [val,setVal] = useState('');const [arr,setArr] = useState<number[]>([]);const [pending,transition] = useTransition()const getList = (e:any) => {setVal(e.target.value)let arr = Array.from(new Array(10000),item=>Date.now())transition(()=>{setArr(arr);})}return (<div className={styles.box}><input value={val} onChange={getList}/>{pending?<h2>loading....</h2>:(arr.map((item,key)=>(<div key={key}>{item}</div>)))}</div>)
}
export default Home;
现象:我们根据useTransition返回的pending状态添加判断,如果列表在渲染中就添加提示加载状态,否则正常显示列表。
2. useDeferredValue——让状态滞后派生
useDeferredValue 的实现效果也类似于 transtion,当迫切的任务执行后,再得到新的状态,而这个新的状态就称之为 DeferredValue。
useDeferredValue 接受一个参数 value ,一般为可变的 state , 返回一个延时状态 deferrredValue。
比如:我们每次的输入或者变化,都会触发一次更新
输入框的值变化的时候,我们使用setTimeout
来模拟下向后端请求数据,或者大量计算的耗时操作。这个时候只要输入框的内容发生变化就会触发useEffect
,我们用count
来进行计数。
import React, {useState, useEffect} from 'react';const List = (props) => {const [list, setList] = useState([]);const [count, setCount] = useState(0);useEffect(() => {setCount(count => count + 1);setTimeout(() => {setList([{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},]);}, 500);}, [props.text]);return [<p>{'我被触发了' + count + '次'}</p>, <ul>{list.map(item => <li>Hello:{item.name} value:{item.value}</li>)}</ul>]
};export default function App() {const [text, setText] = useState("喵爸");const handleChange = (e) => {setText(e.target.value);};return (<div className="App"><input value={text} onChange={handleChange}/><List text={text}/></div>);
};
改造后:
import React, {useState, useEffect,useDeferredValue} from 'react';
import {useDebounce} from 'ahooks';const List = (props) => {const [list, setList] = useState([]);const [count, setCount] = useState(0);useEffect(() => {setCount(count => count + 1);setTimeout(() => {setList([{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},{name: props.text, value: Math.random()},]);}, 500);}, [props.text]);return [<p>{'我被触发了' + count + '次'}</p>, <ul>{list.map(item => <li>Hello:{item.name} value:{item.value}</li>)}</ul>]
};export default function App() {const [text, setText] = useState("喵爸");const deferredText = useDeferredValue(text,{timeoutMs:2000}); const debounceText = useDebounce(text,{wait:2000});useDebounceconst handleChange = (e) => {setText(e.target.value);};return (<div className="App"><input value={text} onChange={handleChange}/><List text={deferredText}/>//比较<List text={debounceText}/></div>);
};
useDeferredValue 和上述 useTransition 本质上有什么异同呢?
相同点:useDeferredValue 本质上和内部实现与 useTransition 一样都是标记成了过渡更新任务。不同点:useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 一个是处理一段逻辑,另一个是生产一个新的状态。
3.useSyncExternalStore——订阅外部数据源,触发更新
4.useInetionEffect——用于处理css in js缺陷问题
官方对这个函数也不推荐使用,推荐使用useEffect
或者useLayoutEffect
代替。
5.useId
useId是新增的用于生成唯一ID值的hook钩子,可客户端和服务器端都可以使用,同时避免 dehydrate 过程中数据不匹配的问题。它主要用于与需要唯一 ID 的可访问性 API 集成的组件库。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为新的流式服务器渲染器如何无序交付 HTML。
react18的改进的API
1.setState 都为异步
setState是异步更新的,setState((pre)=>…)此种更新方式将会依赖上一次的状态值,多个state更新会进行批处理更新,减少渲染次数。
import React from 'react';
const Welcome = () => {const [myState, setMyState] = React.useState(1);const [flag, setFlag] = React.useState(true);console.log('render');const handleClick = () => {setMyState((myState) => myState + 6);console.log('myState1', myState);setMyState(myState + 1);setMyState((myState) => myState + 6);console.log('myState2', myState);setFlag((flag) => !flag);setTimeout(() => {setMyState(myState + 1);console.log('myState3', myState);setMyState((myState) => myState + 10);console.log('myState4', myState);}, 1000);};return (<div className="content-box" style={{ height: 'calc(100% - 30px)' }}><h1>Hello world!</h1><p><button onClick={handleClick}>myState:{myState},flag:{flag ? 'true' : 'false'}</button></p></div>);
};
export default Welcome;
2.Suspense——结合异步组件实现条件渲染
import React, { Suspense, useEffect, useState } from 'react'
import User from './pages/User'// 网络请求
// 返回值为 Promise
const fetchUser = async () => {let ret = await (await fetch('/users.json')).json()return ret
}// 创建一个用于解析promise中数据的方法 仿promise的3个状态
const wrapperPromise = promise => {// 定义一个promise的状态let status = 'pending'// 它就是promise解析出来的数据接受的变量let resultconst callbackPromise = promise.then(ret => {// promise执行成功的,返回成功的状态,并把数据赋值给resultstatus = 'success'result = ret},err => {// 把状态修改为失败,并把错误赋值给resultstatus = 'error'result = err})return {// 此方法中,才是把数据获取到read() {if (status === 'pending') {// 抛一个异常,这样它就会再来执行,此时就会有上一次的结果throw callbackPromise} else if (status === 'success') {return result} else if (status === 'error') {return result}}}
}
const User = ({ users }) => {// 通过此方法把promise中的数据读取出来let data = users.read()return (<div><ul>{data.map(item => (<li key={item.id}>{item.name}</li>))}</ul></div>)
}const App = () => {let [data, setData] = useState(wrapperPromise(fetchUser()))return (<div><Suspense fallback={<div>加载中 .......................................</div>}><User users={data} /></Suspense></div>)
}export default App