useEvent顯著降低Hooks負擔的原生Hook

前言

推薦閱讀:# useMemo..一把梭?達咩!✋|一文告訴你為什麼React不把他們設為默認方法 #useEvent 是一個剛剛提案的原生Hook,還處於RFC。討論地址在這裡~下面有些代碼就是來自其中

RFC:Request for Comments 提案還在廣泛的討論階段

沒有 useEvent 的時候😶

我們先看看不用 useEvent 的情況:

function Chat() {
  const [text, setText] = useState('');
  // 🟡 Always a different function
  const onClick = () => {
    sendMessage(text);
  };
  return <SendButton onClick={onClick} />;
}

其中點擊事件的回調函數 onClick 中需要讀取當前鍵入的文本text,這裡的onClick隨著組件重新渲染一次次地重新創建,每次都會是不一樣的引用,這顯然帶來瞭性能損耗,如果你想對其進行優化,你可能會這樣做:

function Chat() {
  const [text, setText] = useState('');
  // 🟡 A different function whenever `text` changes
  const onClick = useCallback(() => {
    sendMessage(text);
  }, [text]);
  return <SendButton onClick={onClick} />;
}

通過 useCallback 返回一個 memoized 回調函數。

useCallback: 返回一個 memoized 回調函數。 把內聯回調函數及依賴項數組作為參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新。當你把回調函數傳遞給經過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。 useCallback(fn, deps) 相當於 useMemo(() => fn, deps)

最終使得onClick的引用始終不變但是!

onClcik這個方法有需要保證每次都要拿到最新的、正確的text,所以他的deps中就自然是設置瞭text—— 壞瞭,“又回到最初的起點~”。隨著每一次keystrokeonClick又變成瞭上面的情況:

 Always a different function

但你又不能將其從deps中移除,移除瞭他就隻能拿到text的初始值,失去瞭他本該有的功能…

小 useEvent 來給他整個活😎

useEvent就是為瞭解決此類問題,所以他幹脆不要deps瞭,他就是一直返回一個相同的函數引用,哪怕text發生變化。當然,保證它也能拿到最新的、正確的**text**

function Chat() {
  const [text, setText] = useState('');
  // ✅ Always the same function (even if `text` changes)
  const onClick = useEvent(() => {
    sendMessage(text);
  });
  return <SendButton onClick={onClick} />;
}

現在好瞭:

  • onClick 的引用始終是同一個
  • 保證每次都能拿到最新的、正確的 text

當然還有其他一些場景,但是大致需求原理相同,就是不想讓A因為b變化而總是重新加載,但是又因為要拿到b恰當的值,所以deps中必須b,導致不得不重新加載,掉進瞭“圈圈圓圓圈圈~”的陷阱。更多場景這裡就不再贅述。更多案例可查看文末的學習資源~

總而言之,用useEvent給他裹上就是香,就是可以同時達到上面兩個效果:

  • 引用不變
  • 拿到恰當的值

這是咋做到的🌝

說瞭這麼多,我們來看看他這是咋做到的
大概是這麼個形狀:(不是源碼就長這樣的意思嗷)

// (!) Approximate behavior
function useEvent(handler) {
  const handlerRef = useRef(null);
  // In a real implementation, this would run before layout effects
  useLayoutEffect(() => {
    handlerRef.current = handler;
  });
  return useCallback((...args) => {
    // In a real implementation, this would throw if called during render
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

先回顧幾個Hook相關知識點:

useRef

useRef:

useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內持續存在。

這裡通過 useRef 保存回調函數handlerhandlerRef.current,然後再在 useCallback 中從handlerRef.current來取函數再調用,這樣避免瞭直接調用,跳出瞭閉包陷阱。並且不出意外的話handler在整個生命周期內持續存在,也就是隻有一個引用

useLayoutEffect

這個 useLayoutEffect 可能沒那麼常用,我們來看看這是啥嘞

其函數簽名與 useEffect 相同,但它會在所有的 DOM 變更之後同步調用 effect。可以使用它來讀取 DOM 佈局並同步觸發重渲染。在瀏覽器執行繪制之前,useLayoutEffect 內部的更新計劃將被同步刷新。

useEffect

回顧一下 useEffect

默認情況下,effect 將在每輪渲染結束後執行

兩者的區別

好瞭,現在我給你用一個字總結一下兩者區別,useLayoutEffect 更“快”!這個“塊”不是速度更快,而是他“搶跑”瞭哩。useLayoutEffect 是在render之前同步執行,useEffectrender之後異步執行,這裡就是保證useLayoutEffect 裡的回調肯定比useEffect更早前被調用、被執行。

useCallback執行時機

前面說到

useCallback(fn, deps) 相當於 useMemo(() => fn, deps)

文檔裡是這樣說 useMemo 的:

記住,傳入 useMemo 的函數會在渲染期間執行。請不要在這個函數內部執行與渲染無關的操作,諸如副作用這類的操作屬於 useEffect 的適用范疇,而不是 useMemo。

也就是他是在render執行的,也就是保證瞭賦值handlerhandlerRef.current是在前面發生

這裡的作用

這裡返回的是一個useCallback包裹後 memoized函數,其中從handlerRef.current中獲取函數,並且deps[],也就是說他不會再次更新。

捋一捋🌊

回顧完知識點我們也就濾清瞭這個useEvent方法,一句話總結就是:它接收一個回調函數handler作為參數,提供給你一個穩定的函數(始終隻有一個引用)並且調用時都是用的你傳入的最新的參數...args——比如前面案例中的text,始終都是最新的、正確的、恰當的。再結合一開始的案例,大概流程就是這樣:

不過目前這個也是 5.4號剛剛提出,也就是昨天,在正式生產環境中使用應該還早,但我希望你還是能從本文有所收獲滴~

以上就是useEvent顯著降低Hooks負擔的原生Hook的詳細內容,更多關於useEvent降低Hooks負擔原生Hook的資料請關註WalkonNet其它相關文章!

推薦閱讀: