createRoot
react18更新修改了根节点的渲染方式
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
自动批处理优化
在 v18 之前只在事件处理函数中实现了批处理,在 v18 中所有更新都将自动批处理,包括 promise
链、setTimeout
等异步代码以及原生事件处理函数
批处理: React将多个状态更新分组到一个重新渲染中以获得更好的性能。(将多次 setstate 事件合并)
渲染优先级
在react18中可以指定渲染优先级,对于低优先级的会被高优先级的渲染中断
新Hooks
useId
是一个新的钩子,用于在客户端和服务器上生成唯一的 ID,同时避免水合作用不匹配。它主要用于与需要唯一 ID 的辅助功能 API 集成的组件库。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为新的流式服务器渲染器如何无序地交付 HTML。
当一个组件,同时会被服务端和客户端渲染时,我们就可以使用 useId
来创建当前组件的唯一身份。
import './App.css';
import { useDeferredValue, useId, useInsertionEffect, useRef, useState } from 'react';
function App() {
let ref = useRef()
const [state, setState] = useState(0)
let id = useId() // 每次渲染id的值都是固定的,格式:r3:、:r4:
let id1 = useId()
let id2 = useId()
let fn = () => {
setState(state + 1)
}
return (
<div className="App">
<div ref={ref}>
{id}
<br></br>
{id}
<br></br>
{id2}
<br ></br>
{state}
</div>
<button onClick={fn}>+1</button>
</div>
);
}
export default App;
startTransition
允许您将某些状态更新标记为不紧急。默认情况下,其他状态更新被视为紧急更新。React 将允许紧急状态更新(例如,更新文本输入)中断非紧急状态更新(例如,呈现搜索结果列表)。
import './App.css';
import { useDeferredValue, useInsertionEffect, useRef, useState, useTransition } from 'react';
function App() {
let ref = useRef()
const [state, setState] = useState(0)
const [state1, setState1] = useState(1)
console.log("state",state)
console.log("state1",state1)
for(let i = 0;i<999;i++){ // 模拟线程繁忙
console.log(i)
}
let [isPending,startTransition] = useTransition()
let fn = () => {
setState(state + 1)
// setState1(state1 + 1) 假设:同时对state和state1的更新会导致卡顿,如果将transition注释,同时解开setState1(state1 + 1)。那么就满足了假设条件。因为react会将setState(state + 1)和setState1(state1 + 1)合并,函数重新执行一次。满足同时对state和state1的更新。所以我们可以将setState1(state1 + 1)放在transition中执行,但是要注意的是造成的函数执行是可中断的、延迟的
startTransition(()=>{ // 在transition里修改state造成的更新和useDeferredValue是一样的可中断的、延迟的。
// 证明:快速点击+1按钮会发现state的值会大于state1
setState1(state1 + 1)
})
}
const handleChange = (e) => {
// setState(e.target.value);
};
return (
<div className="App">
<input value={state} onChange={handleChange} />
<div ref={ref}>
{state}
<br></br>
{state1}
</div>
<button onClick={fn}>+1</button>
</div>
);
}
export default App;
useDeferredValue
允许您延迟重新渲染树的非紧急部分。它类似于去抖动,但与之相比有一些优点。没有固定的时间延迟,因此 React 将在第一个渲染反映在屏幕上后立即尝试延迟渲染。延迟的呈现是可中断的,不会阻止用户输入。
import './App.css';
import { useDeferredValue, useInsertionEffect, useRef, useState } from 'react';
function App() {
let ref = useRef()
const [state, setState] = useState(0)
let s = useDeferredValue(state)
console.log("useDeferredValue",s)
for(let i = 0;i<999;i++){
console.log(i)
}
let fn = () => {
setState(state + 1)
}
useInsertionEffect(() => {
console.log(ref)
})
const handleChange = (e) => {
setState(e.target.value);
};
return (
<div className="App">
<input value={state} onChange={handleChange} />
<div ref={ref}>
<div>{s}</div>
<br></br>
{state}
</div>
<button onClick={fn}>+1</button>
</div>
);
}
export default App;
// 连续多次点击按钮,查看s的值,可以发现s的值并发可中断的。所以我们可以将一些渲染等级较低的数据放在useDeferredValue中。
结论
useDeferredValue和setState一样设置新值,会导致函数组件重新执行渲染,但是useDeferredValue导致的渲染是可中断(不可恢复)的(高优先级需要渲染)、延迟的(空闲时渲染)
useSyncExternalStore
它允许外部存储通过强制对存储的更新是同步的来支持并发读取。它消除了在实现对外部数据源的订阅时的需要,并且建议用于与 React 外部状态集成的任何库。
import {
createRef,
useState,
startTransition,
useSyncExternalStore
} from "react";
function getItems(length) {
const items = new Array(length);
items.fill("");
return items;
}
function createStore() {
const ref = createRef();
ref.current = { x: 0 };
window.addEventListener("mousemove", (e) => {
ref.current.x = e.clientX;
});
return ref.current;
}
function useX(store) {
// return store.x;
return useSyncExternalStore(
() => { console.log("更新完成",store.x) }, // 数据一致被确定时执行该函数,在这里store.x是固定的值
() => {
console.log(store.x) // 数据不一致未被确定时执行该函数,在这里store.x是变化的值
return store.x //
}
);
}
const myStore = createStore();
function MouseX() {
const x = useX(myStore);
const now = performance.now();
while (performance.now() - now < 10) { }
return <li>{x}</li>;
}
export default function App() {
const [count, setCount] = useState(0);
function onClick() {
// setCount(count + 1);
startTransition(() => {
setCount(count + 1);
});
}
return (
<div className="App">
<button onClick={onClick}>Update X: {count}</button>
<ul>
{getItems(30).map((item, index) => (
<MouseX key={index} />
))}
</ul>
</div>
);
}
useInsertionEffect
是一个新的钩子,它允许CSS-in-JS库解决在渲染中注入样式的性能问题。除非你已经构建了一个CSS-in-JS库,否则我们不希望你使用它。此挂接将在 DOM 发生突变后运行,但在布局效果读取新布局之前运行。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为 React 在并发渲染期间屈服于浏览器,使其有机会重新计算布局。
使用方式和useEffect一样,只不过只是时机不一样,并且无法获取最新ref,一般在此(DOM改变前)修改一些样式