【每日一題NO.66】useEffect、useCallback、useMemo理解及仿寫

useEffect
useEffect 一般用于處理狀態(tài)更新導(dǎo)致的副作用。
useEffect 可以看做是 componentDidMount/componentDidUpdate/componentWillUnmount 這三個生命周期函數(shù)的替代。
語法:
useEffect(didUpdate,?deps);
didUpdate
是一個包含命令式并且可能會有副作用代碼的函數(shù),是組件渲染成功并且 deps 依賴參數(shù)發(fā)生變化時(shí)執(zhí)行的函數(shù)。該函數(shù)可以沒有返回值,只是執(zhí)行內(nèi)部的內(nèi)容。
但是當(dāng) didUpdate 有返回值的時(shí)候,返回值必須是一個可執(zhí)行的函數(shù),目的是用于清除 didUpdate 執(zhí)行過程中產(chǎn)生的訂閱或者計(jì)數(shù)器ID等資源。
如果 didUpdate 多次觸發(fā),則在每次重新執(zhí)行前都會先行返回的可執(zhí)行函數(shù),官方稱之為清除effect。
以下是官網(wǎng)提供的例子,可以全面展示 useEffect 的使用方式:
import?React,?{?useState,?useEffect?}?from?"react";
//?該組件定時(shí)從服務(wù)器獲取好友的在線狀態(tài)
function?FriendStatus(props)?{
??const?[isOnline,?setIsOnline]?=?useState(null);
??useEffect(()?=>?{
????function?handleStatusChange(status)?{
??????setIsOnline(status.isOnline);
????}
????//?在瀏覽器渲染結(jié)束后執(zhí)行
????ChatAPI.subscribeToFriendStatus(props.friend.id,?handleStatusChange);
????//?在每次渲染產(chǎn)生的?effect?執(zhí)行之前執(zhí)行
????return?function?cleanup()?{
??????ChatAPI.unsubscribeFromFriendStatus(props.friend.id,?handleStatusChange);
????};
????//?只有?props.friend.id?這個依賴更新了才會重新執(zhí)行這個?hook
??},?[props.friend.id]);
??if?(isOnline?===?null)?{
????//繼續(xù)等待
????return?"Loading...";
??}
??return?isOnline???"Online"?:?"Offline";
}
仿寫useEffect
let?hookStates?=?[];?//?保存狀態(tài)的數(shù)組?[0,0]
let?hookIndex?=?0;?//?索引
function?useEffect(callback,dependencies){
??if(hookStates[hookIndex]){
????//?說明不是第一次執(zhí)行
????let?lastDependencies?=?hookStates[hookIndex];
????let?same?=?dependencies.every((item,index)?=>?item?===?lastDependencies[index]);
????if(same){
??????hookIndex++;
????}else{
??????hookStates[hookIndex++]?=?dependencies;
??????callback();
????}
??}else{
????//?說明是第一次渲染
????hookStates[hookIndex++]?=?dependencies;?//?進(jìn)行依賴緩存
????callback();
??}
}
useLayoutEffect
useEffect 是官方推薦拿來代替componentDidMount/componentDidUpdate/componentWillUnmount這三個生命周期函數(shù)的,但是它們并不是完全等價(jià)的:
useEffect 是在瀏覽器渲染結(jié)束之后才執(zhí)行的, 這三個生命周期函數(shù)是在瀏覽器渲染之前同步執(zhí)行的。
而能夠完美等價(jià)這三個生命周期函數(shù)的另一個官方hook就是 useLayoutEffect。
useEffect 是在瀏覽器重繪之后才異步執(zhí)行的,而 useLayoutEffect 是在瀏覽器重繪之前同步執(zhí)行的。
因?yàn)?useEffect 不會阻塞瀏覽器重繪,而且平時(shí)業(yè)務(wù)中遇到的絕大多數(shù)場景都是時(shí)機(jī)不敏感的,比如取數(shù)、修改 dom、事件觸發(fā)/監(jiān)聽…,所以首先推薦使用 useEffect 來處理 副作用,性能上表現(xiàn)的更好一些。
useEffect(()?=>?{
??const?timer?=?setInterval(()?=>?{
????console.log("timer");
??},?1000);
??return?()?=>?{
????//?清除定時(shí)器
????clearInterval(timer);
??};
});
useCallback
語法:
const?memoizedCallback?=?useCallback(()?=>?{
??doSomething(params);
},?deps);
deps 是依賴的參數(shù)列表,當(dāng)依賴列表中的任一參數(shù)變化時(shí),則重新執(zhí)行前面的函數(shù)
返回一個 memoize 的回調(diào)函數(shù),即返回一個函數(shù)的句柄,等同于函數(shù)的變量,因此 useCallback 的作用在于利用 memoize 減少無效的 re-render 來達(dá)到性能優(yōu)化的作用。
有人會誤以為 useCallback 可以用來解決創(chuàng)建函數(shù)造成的性能問題,其實(shí)恰恰相反。單個組件來看,useCallback 只會更慢,因?yàn)?inline 函數(shù)是無論如何都會創(chuàng)建的,還增加了 useCallback 內(nèi)部對 inputs 變化的檢測。
useCallback 的真正目的是在于緩存每次渲染時(shí) inline callback 的實(shí)例,這樣方便配合上子組件的 shouldComponentUpdate 或者 React.memo 起到減少不必要渲染的作用,需要注意的是 React.memo 和 React.useCallback 一定要配對使用。缺了一個可能導(dǎo)致性能不升反降。畢竟無意義的淺比較也是消耗那么一點(diǎn)點(diǎn)性能。
仿寫
let?hookStates?=?[];?//?保存狀態(tài)的數(shù)組?[0,0]
let?hookIndex?=?0;?//?索引
function?useCallback(callback,?dependencies)?{
??if?(hookStates[hookIndex])?{?//?說明不是第一次,
????let?[lastCallback,?lastDependencies]?=?hookStates[hookIndex];
????//?判斷一下新的依賴數(shù)組中的每一項(xiàng)是否跟上次完全相等
????let?same?=?dependencies.every((item,?index)?=>?item?===?lastDependencies[index]);
????if?(same)?{
??????hookIndex++;
??????return?lastCallback;
????}?else?{?//?只要有一個依賴變量不一樣的話
??????hookStates[hookIndex++]?=?[callback,?dependencies];?//?hookIndex=3?callback=()=>setNumber(number+1)??dependencies=[0]
??????return?callback;
????}
??}?else?{?//?說明是第一次渲染
????hookStates[hookIndex++]?=?[callback,?dependencies];?//?hookIndex=3?callback=()=>setNumber(number+1)??dependencies=[0]
????return?callback;
??}
}
useMemo
語法:
const?memoizedValue?=?useMemo(()?=>?computerExpensiveValue(params),?deps);
返回一個 memoize 值,useMemo 函數(shù)每當(dāng) deps 發(fā)生變化時(shí)都會調(diào)用 componentExpensiveValue 的內(nèi)容,這是和 useCallback 最大的不同,useCallback 不執(zhí)行 dosomething 的內(nèi)容,只是重新刷新函數(shù)句柄。
官方文檔上有這樣的一個等式:
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)。
當(dāng) deps 發(fā)生變化時(shí),useCallback 返回的值是一個可執(zhí)行的 fn 的句柄,而 useMemo 則是執(zhí)行()=>fn。
useMemo 是拿來保持一個對象引用不變的。useMemo 和 useCallback 都是 React 提供來做性能優(yōu)化的。比起 class,Hooks 給開發(fā)者更高的靈活度和自由,但是對開發(fā)者要求也更高了,因?yàn)?Hooks 使用不當(dāng)很容易導(dǎo)致性能問題。
仿寫
let?hookStates?=?[];?//?保存狀態(tài)的數(shù)組?[0,0]
let?hookIndex?=?0;?//?索引
function?useMemo(factory,?dependencies)?{
??if?(hookStates[hookIndex])?{?//?說明不是第一次,
????let?[lastMemo,?lastDependencies]?=?hookStates[hookIndex];
????//?判斷一下新的依賴數(shù)組中的每一項(xiàng)是否跟上次完全相等
????let?same?=?dependencies.every((item,?index)?=>?item?===?lastDependencies[index]);
????if?(same)?{
??????hookIndex++;
??????return?lastMemo;
????}?else?{?//?只要有一個依賴變量不一樣的話
??????let?newMemo?=?factory();
??????hookStates[hookIndex++]?=?[newMemo,?dependencies];
??????return?newMemo;
????}
??}?else?{?//?說明是第一次渲染
????let?newMemo?=?factory();
????hookStates[hookIndex++]?=?[newMemo,?dependencies];
????return?newMemo;
??}
}
所有《每日一題》的 知識大綱索引腦圖 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以點(diǎn)擊文末的 “閱讀原文” 快速跳轉(zhuǎn)

