改进已有属性,如自动批量处理【setState】、改进Suspense、组件返回undefined不再报错等
支持Concurrent模式,带来新的API,如useTransition、useDeferredValue等
npm install react@latest react-dom@latest
npm 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();
创建根以渲染或卸载的新方法
useDeferredValue
/useTransition
react17 和 react18 的区别就是:从同步不可中断更新变成了异步可中断更新,react17可以通过一些试验性的API开启并发模式,而react18则全面开启并发模式。
并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,该模式通过使渲染可中断来修复阻塞渲染限制。在并发模式中,React 可以同时更新多个状态。
Automatic Batching自动批量更新state,减少渲染次数
如何禁用(不推荐)—— 可以使用ReactDom;提供的flushSync方法
import {flushSync} from 'react-dom'
function handleClick(){
flushSync(()=>{
setCounter(c=>c+1);
})
// React has updated the DOM by now
flushSync(()=>{
setFlag(f=>!f);
})
// React has updated the DOM by now
}
新的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状态添加判断,如果列表在渲染中就添加提示加载状态,否则正常显示列表。
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});useDebounce
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div className="App">
<input value={text} onChange={handleChange}/>
<List text={deferredText}/>
//比较
<List text={debounceText}/>
</div>
);
};
相同点:useDeferredValue 本质上和内部实现与 useTransition 一样都是标记成了过渡更新任务。
不同点:useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 一个是处理一段逻辑,另一个是生产一个新的状态。
官方对这个函数也不推荐使用,推荐使用useEffect
或者useLayoutEffect
代替。
useId是新增的用于生成唯一ID值的hook钩子,可客户端和服务器端都可以使用,同时避免 dehydrate 过程中数据不匹配的问题。它主要用于与需要唯一 ID 的可访问性 API 集成的组件库。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为新的流式服务器渲染器如何无序交付 HTML。
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;
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 result
const callbackPromise = promise.then(
ret => {
// promise执行成功的,返回成功的状态,并把数据赋值给result
status = 'success'
result = ret
},
err => {
// 把状态修改为失败,并把错误赋值给result
status = '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