这篇会介绍 React 的一个新模式 Concurrent Mode 以及关於它的一些功能。
它由数个新的 React 功能组成,不过这些功能都还在实验阶段。
目前 React 有三种模式,由图可以看到原本的 React 程序码变成了 Legacy Mode,另外还有夹在两个模式过渡的 Blocking Mode。在 Concurrent Mode 的部分,新增了几个新的功能/特性,本文将会介绍标题提到的那几项功能。
如果想要先试用这个新功能的话,需先安装:
npm install react@experimental react-dom@experimental
原本在根元件的程序也要修改:
import ReactDOM from 'react-dom';
const root = document.getElementById('root');
// 旧
ReactDOM.render(<App />, root);
// 新
ReactDOM.createRoot(root).render(<App />);
Concurrent Mode API Reference (Experimental)
在了解 Fiber 这个东西前,先简单复习一下 Reconciliation。
在 React 中因为状态的改变而要重新渲染时,一连串决定哪些 DOM 元素要做更新的过程,就是 Reconciliation。包含以下的步骤:
一个功能的产生想必是为了解决某个问题,Fiber 也是如此,所以先了解一下在 Fiber 出现前的 React 有什麽问题?
在 React V15 中的 React,使用的是 Stack reconciler 去比对更新前後的 DOM Tree 哪里不一样。
当进行 Reconciliation 时,比对两个 Virtual DOM Tree 不同的部分是透过从 DOM Tree 的根节点不断做递回将整个 DOM Tree,并搭配上 call stack(堆叠)的方式和另一个 DOM Tree 做比对,以找出不同之处。
若 Tree 的结构较为复杂的话,运作这些步骤将会很花时间,来不及在一次 frame 时间内处理完时就会掉帧。
Fiber 便是为了解决效能和渲染时的流程度而生。
在 React V16 中,Fiber reconciler 取代了 Stack reconciler,这个东西可以根据整个要渲染的 DOM Tree 创造出多个小节点,将整个渲染的任务拆分成许多的小任务,也就是这次要介绍的主角: Fiber。
由 Fiber 构成的渲染任务可以被中断、重用、舍弃,并且不同的渲染任务可以被设定优先权做渲染。
而单独的一个 Fiber 有点类似 React element 的概念,会对应到 Virtual DOM 的元素,并且 Fiber 也能储存一些元件的资料和状态(props、state、updateQueue...),关於 Fiber 相关的属性可以参考以下 React 的原始码:
很多个 Fiber 会构成一个 Fiber Tree,其资料结构为 Linked List,以下是简单的范例:
另外推荐一个网页,呈现了用 Stack reconciler 和 Fiber reconciler 两个方式频繁改变 Sierpinski triangle 内容的差异,可以看到 Stack reconciler 的方式会造成明显的卡顿,Fiber reconciler 则不会。
Day23 的文章我有提到在 React 渲染时,会分成 Render Phase & Commit Phase,在这两个阶段时,Fiber 也会进行一些事情。
在此阶段 Fiber reconciler 会建立 Fiber,产生 Fiber Tree,,若有需要更新的 DOM,会再建立出一个新的 Fiber Tree,称为 workInProgress Tree。
在此阶段,会为 Fiber 执行在它们上面记录的 side effect,并且在此阶段运行时不可以中断。
当 workInProgress Tree 完成更新时,会变成新的 Fiber Tree,等下次更新时又会出现新的 orkInProgress Tree,如此循环下去,这样的过程称为 Double Buffering。
在 React V16 中,废弃了 componentWillMount、componentWillUpdate、componentWillReceiveProps,这点在 Day22 有稍微提到,而这些生命周期函式被废弃就和 Fiber 的出现有所关联。
由於这些函式都出现在 Render 阶段,在前面说到渲染任务可以被中断、重用、舍弃,并且不同的渲染任务可以被设定优先权做渲染,所以这些废弃的生命周期函式都可能被重复执行,
以下的影片和文章都解说的蛮详细的,若有兴趣更加了解的读者可以进一步阅读。
常和 Suspense 搭配使用,它可以延迟渲染指定的元件,让优先度较低或是比较要耗时的元件稍後渲染。
另外如果 loading 状态过短,使用者可能在网页上看到载入图示只有一瞬间的时间,那这样其实也不太需要 Loading 元件,此时透过 useTransition 延时也可以避免不必要的 Loading 元件呈现在网页。
const [startTransition, isPending] = useTransition({ timeoutMs: 指定的时间 });
程序码来自官网范例,在这段程序码中,点击按钮时会去取得 profile 的资料,但因为将设定取回资料的 setResource 放在了 startTransition 里面,所以会延迟更新 state,而在更新 state 前,会先出现 isPending 条件式的内容。
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition({ timeoutMs: 2000 });
return (
<>
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
});
}}
>
Next
</button>
{isPending ? " Loading..." : null}
<Suspense fallback={<Spinner />}>
<ProfilePage resource={resource} />
</Suspense>
</>
);
}
const deferredValue = useDeferredValue(value, { timeoutMs: 指定的时间 });
程序码来自官网范例,input 输入框的值会随着使用者打字不断快速改变,但 MySlowList 会等待两秒後才会进行更新。
function App() {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, { timeoutMs: 2000 });
return (
<div className="App">
{/* Keep passing the current text to the input */}
<input value={text} onChange={handleChange} />
...
{/* But the list is allowed to "lag behind" when necessary */}
<MySlowList text={deferredText} />
</div>
);
}
Suspense 在之前的 Day24 时就有介绍过,不过在 Concurrent Mode 出现之前,就有这个元件了,但只有动态载入的功能,而在 Concurrent Mode 中,增加了可以加入 Loading 状态的元件,资料持续载入的期间就先暂时渲染 Loading 元件。
终於只剩最後一天的铁人赛!明天会整理一些不错的 youtube react 学习资源清单给读者做更多的延伸学习和参考~
>>: # Day28--让commit像战国时代一样分分合合
其实只是拖延症点到满等的我,说是主角其实只是拖延症发作 我通常都是先撰写前端篇才写後端篇,所以看官们...
我很早就开始接触组合语言,但没有学太久,就没有再碰了,当初学组合语言的原因,是觉得组合语言是人与机器...
该文章同步发布於:我的部落格 还记得我们使用 let 方法来实作一个物件来让我们可以快速使用! 但...
怎麽有东西挡在前面 昨天有提到为什麽点击 icon 放大之後,还是有一些其他 icon 在上面呢? ...
大家好,我是YIYI,今天要来分享我设计的APP的PHOTOTYPE制做过程。 今日进度 今天的进度...