React18新特性
Render Api
为了更好的管理root节点
,React 18
引入了一个新的 root API
,新的 root API
还支持 new concurrent renderer
(并发模式的渲染),它允许你进入concurrent mode
(并发模式)。
// React 17
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const root = document.getElementById('root')!;
ReactDOM.render(<App />, root);
// React 18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = document.getElementById('root')!;
ReactDOM.createRoot(root).render(<App />);
setState 批处理
在React18之前,只有在React事件处理函数中会进行批处理更新
以下代码在18及18之前的版本,都只会打印一次render
import React, { useState } from 'react';
const BatchSetState = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
console.log('render');
return (
<div>
<button
onClick={() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
}}
>
点击
</button>
</div>
);
};
export default BatchSetState;
而在promise, setTimeout, 原生事件处理函数或其他任何事件内都不会批处理
以下代码,在18的版本中只会打印一次render, 而在之前的版本,会打印两次
import React, { useEffect, useState } from 'react';
const BatchSetState2 = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
// useEffect(() => {
// document.body.addEventListener('click', () => {
// setCount1((count) => count + 1);
// setCount2((count) => count + 1);
// });
// // 在原生js事件中不会进行批处理
// }, []);
console.log('render');
return (
<div>
<button
onClick={() => {
setTimeout(() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
}, 100);
}}
>
点击
</button>
</div>
);
};
export default BatchSetState2;
flushSync
批处理是一个破坏性改动,想退出批量更新,可以用flushSync
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
const FlushSync = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
console.log('render');
return (
<div>
<button
onClick={() => {
flushSync(() => {
setCount1(count1 + 1);
});
flushSync(() => {
setCount2(count2 + 1);
});
}}
>
点击
</button>
</div>
);
};
export default FlushSync;
效果如下,会打印两次render
需要注意的是,一个flushSync里的多个setState,还是会批处理更新
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
const FlushSync2 = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
console.log('render');
return (
<div>
<button
onClick={() => {
flushSync(() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
});
}}
>
点击
</button>
</div>
);
};
export default FlushSync2;
效果如下
startTransition
startTransition
,主要为了能在大量的任务下也能保持 UI 响应。这个新的 API 可以通过将特定更新标记为“过渡”
来显著改善用户交互,简单来说,就是被 startTransition
回调包裹的 setState
触发的渲染被标记为不紧急渲染,这些渲染可能被其他紧急渲染
所抢占。
import React, { useState, useEffect, useTransition } from 'react';
const App: React.FC = () => {
const [list, setList] = useState<any[]>([]);
const [isPending, startTransition] = useTransition();
useEffect(() => {
// 使用了并发特性,开启并发更新
startTransition(() => {
setList(new Array(10000).fill(null));
});
}, []);
return (
<>
{list.map((_, i) => (
<div key={i}>{i}</div>
))}
</>
);
};
export default App;
useDeferredValue
返回一个延迟响应的值,可以让一个state
延迟生效,只有当前没有紧急更新时,该值才会变为最新值。useDeferredValue
和 startTransition
一样,都是标记了一次非紧急更新。
从介绍上来看 useDeferredValue
与 useTransition
是否感觉很相似呢?
- 相同:
useDeferredValue
本质上和内部实现与useTransition
一样,都是标记成了延迟更新
任务。 - 不同:
useTransition
是把更新任务变成了延迟更新任务,而useDeferredValue
是产生一个新的值,这个值作为延时状态。(一个用来包装方法,一个用来包装值)
所以,上面 startTransition
的例子,我们也可以用 useDeferredValue
来实现:
import React, { useState, useEffect, useDeferredValue } from 'react';
const App: React.FC = () => {
const [list, setList] = useState<any[]>([]);
useEffect(() => {
setList(new Array(10000).fill(null));
}, []);
// 使用了并发特性,开启并发更新
const deferredList = useDeferredValue(list);
return (
<>
{deferredList.map((_, i) => (
<div key={i}>{i}</div>
))}
</>
);
};
export default App;
参考文档
React18新特性
http://yellowcan.top/2023/05/07/react18-xin-te-xing/