2021年快过完了,年末,总结一下,一些技术的分享。
React 18 Alpha已经发版。主要有以下改变:
1. React 18使用:ReactDOM.createRoot
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
React 17使用:ReactDOM.render
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2. 自动批量处理(automatic batching)--为了性能考虑,如果有多个状态更新,React会统一处理,组件只会渲染一次。
react 17,直接放在事件中,会进行batching,页面只会渲染1次。
function handleClick() { setCount((c) => c + 1); setFlag((f) => !f); }
Demo: React 17 batches inside event handlers.(Notice one render per click in the console.)
react 17,用promise, setTimeout,原生事件,不会进行batching,变量更新几次,页面就会渲染几次。
function handleClick() { fetchSomething().then(() => { setCount((c) => c + 1); setFlag((f) => !f); }); }
Demo: React 17 does NOT batch outside event handlers. (Notice two renders per click in the console.)
注意:对于promise, setTimeout,原生事件,可以通过使用unstable_batchedUpdates,进行batching。但这个方法不提倡,后续会淘汰
import { unstable_batchedUpdates } from 'react-dom'; unstable_batchedUpdates(() => { setCount(c => c + 1); setFlag(f => !f); });
React 18,用promise, setTimeout,原生事件,也会进行batching,页面只会渲染1次。
Demo: React 18 with createRoot batches even outside event handlers! (Notice one render per click in the console!)
注意:如果不想进行batching,可以使用flushSync。
import { flushSync } from 'react-dom'; // Note: react-dom, not react function handleClick() { flushSync(() => { setCounter(c => c + 1); }); flushSync(() => { setFlag(f => !f); }); }
这个改变,对hooks没影响,对class components 有影响。
例如:对于hooks,没有影响。
function handleClick() { setTimeout(() => { console.log(count); // 0 setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); console.log(count); // 0 }, 1000)
例如,在react17中,对class components,可以通过原生事件,setTimeout,将异步改为同步。
handleClick = () => { setTimeout(() => { this.setState(({ count }) => ({ count: count + 1 })); // { count: 1, flag: false } console.log(this.state); this.setState(({ flag }) => ({ flag: !flag })); }); };
但是,在react18中,是不起作用的。
handleClick = () => { setTimeout(() => { this.setState(({ count }) => ({ count: count + 1 })); // { count: 0, flag: false } console.log(this.state); this.setState(({ flag }) => ({ flag: !flag })); }); };
注意:react18中,就需要使用ReactDOM.flushSync ,强制更新。建议尽量不要这么做。
handleClick = () => { setTimeout(() => { ReactDOM.flushSync(() => { this.setState(({ count }) => ({ count: count + 1 })); }); // { count: 1, flag: false } console.log(this.state); this.setState(({ flag }) => ({ flag: !flag })); }); };
3. React 18的SSR的新Suspense
4. React 18的Suspense变化
Suspense--用于数据获取。可以“等待”目标代码加载,并且可以直接指定一个加载的界面(像是个 spinner),让它在用户等待的时候显示。fallback
定义loading显示.
<Suspense fallback={<Loading />}>
<ComponentThatSuspends />
<Sibling />
</Suspense>
React 17 demo sandbox showing all the effects firing too early.
React 17,子组件依然会渲染,会隐藏,等待数据,会消耗性能。
React 18 with createRoot demo sandbox showing effects delayed until the content is ready.
React 18,等拿到数据,子组件才做渲染。
5.SuspenseList--用于控制Suspense组件的显示顺序。
属性 | 值 |
|
|
|
|
import {useState, Suspense, SuspenseList} from "react";
<SuspenseList tail="collapsed">
<Suspense fallback={<h1>loading - user</h1>}>
<User resource={resource} />
</Suspense>
</ErrorBoundaryPage>
<Suspense fallback={<h1>loading-num</h1>}>
<Num resource={resource} />
</Suspense>
</SuspenseList>
6.startTransition--标记某个更新为transition,先短暂停留在当前页面, 只要下一个视图加载完了,再进行切换。
Concurrent模式,就是用户可以自定义更新任务优先级并且能够通知到React,React再来处理不同优先级的更新任务。Concurrent 模式减少了防抖和节流在 UI 中的需求。因为渲染是可以中断的,React 不需要人为地 延迟 工作以避免卡顿(比如使用setTimeout)。它可以立即开始渲染,但是当需要保持应用响应时中断这项工作。
紧急更新:setState, 非紧急更新: startTransition,useTransition,useDeferredValue
如UI从一个视图向另一个视图的更新,停留在当前视图,只要下一个视图加载完了,直接切换就行。
import { startTransition } from "react"; startTransition(() => { refresh(); });
与setTimeout不同
在startTransition
出现之前,我们可以使用setTimeout
来实现优化。但是现在有了startTransition
基本上可以抛弃setTimeout
了,原因主要有以三点:
首先,与setTimeout
不同的是,startTransition
并不会延迟调度,而是会立即执行,startTransition
接收的函数是同步执行的,只是这个update被加了一个“transitions"的标记。而这个标记,React内部处理更新的时候是会作为参考信息的。这就意味着,相比于setTimeout
, 把一个update交给startTransition
能够更早地被处理。而在于较快的设备上,这个过度是用户感知不到的。
使用场景:
-
渲染慢:如果你有很多没那么着急的内容要渲染更新。
- 网络慢:如果你的更新需要花较多时间从服务端获取。这个时候也可以再结合
Suspense
。
7.useTransition--在使用startTransition更新状态的时候,用户可能想要知道transition的实时情况(eg 当前加载中),这个时候可以使用React提供的hook api useTransition
。
import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();
8.useDeferredValue--延迟更新某个不那么重要的部分。保证高优先级的任务,优先执行。
例如:当用户在输入框输入字的时候,用户应该立马看到输入框的反应,相比之下,模糊查询结果可以延迟显示。
import {useDeferredValue, useState} from "react"; const [text, setText] = useState("hello"); const deferredText = useDeferredValue(text);