今天要來介紹的是useDeferredValue,這個hook可以用來延後畫面的更新,幫助我們優化一些像是大量的畫面更新造成的操作卡住問題。可以先看看官方文件提供的這個範例,就可以了解到卡住的意思是什麼,這個範例故意讓每個SlowItem都花1毫秒的時間,來模擬複雜操作造成元件要渲染很久造成卡住的問題。
const deferredValue = useDeferredValue(value)
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
value為要延遲的值,在第一次渲染的時候defferedValue為value提供的值,如果value有更新的話,重新渲染的畫面會以defferedValue舊的value呈現,然後React會在背後排定以新的值再渲染一次,等之前的渲染結束後會更新defferedValue值再執行新值的渲染。
在官網的另個範例可能會更容易了解一點,它比較Input持續key in即時更新的query和延遲的deferredQuery,如果不同就呈現半透明的結果,這兩個變數不一致的時候代表(半透明)deferredQuery新的值在執行操作的時候,等操作完畢後即刻更新deferredQuery,使query和deferredQuery兩者一致,畫面也更新新的值執行結果,也將半透明取消。 如果你持續在key內容進去input的話,useDeferredValue
會放棄舊的值渲染新的值,也就是說我們持續key內容它只會顯示我們停下來不key in之後的內容。
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
再回到我們一開始官網卡住的範例,官網也提供一個解決的方案範例,它使用了useDeferredValue
+memo來解決卡住的問題,在這邊如果我們把SlowList元件的memo拿掉如下,只使用useDeferredValue
的話是無法避免某個元件要執行很久導致卡住的問題,因為useDeferredValue
只是讓我們畫面延遲呈現(deferredText會延後更新),父層狀態有更新重新渲染,子層的元件也是會跟這一起渲染。
const SlowList = function SlowList({ text }) {
// Log once. The actual slowdown is inside SlowItem.
console.log("[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />");
let items = [];
for (let i = 0; i < 250; i++) {
items.push(<SlowItem key={i} text={text} />);
}
return <ul className="items">{items}</ul>;
};
這時候如果子層的SlowList元件搭配memo使用,就可以在deferredText還是舊的值的時候SlowList元件保持不渲染來達到效能優化。讓input可以優先更新SlowList會在input更新完後隨後進行更新,讓input輸入不會卡住。
const SlowList = memo(function SlowList({ text }) {
// Log once. The actual slowdown is inside SlowItem.
console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');
let items = [];
for (let i = 0; i < 250; i++) {
items.push(<SlowItem key={i} text={text} />);
}
return (
<ul className="items">
{items}
</ul>
);
});
export default function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
debouncing和throttling都會有固定延遲時間,useDeferredValue會根據設備的執行速度,如果性能很好的設備它馬上就可以新的值的更新,如果是性能較差的手持裝置可能就會需要延遲比較久才能執行完前面優先的其他畫面更新。另外,useDeferredValue如果正在執行一個畫面更新時,有新的值改變,它可以中斷舊的畫面更新以新的值執行新的畫面更新。但是,debouncing和throttling還是非常的有用,它可以減少例如呼叫api次數等的優化,這點useDeferredValue可無法做到,因為它只會中斷畫面的更新並不會中斷對外呼叫的api連線。
https://react.dev/reference/react/useDeferredValue