React usePrevious 系列:從 useRef 到 useDeferredValue

· 4 min read

問題:如何拿到上一次的值?

在 React 開發中,「比較前後兩次 render 的值」是一個常見需求。比如動畫(需要知道從哪個值變到哪個值)、效能優化(只在特定 prop 變化時執行邏輯)、或偵測變化方向。

最經典的做法是 usePrevious hook,但實作方式比你想像的有更多眉角。

方式一:useRef + useEffect

這是最廣為流傳的版本:

unknown node

原理很簡單:useEffect 在 render 之後才執行,所以在 render 過程中讀到的 ref.current 還是上一次的值。等 render 結束,effect 才把新值存進去。

問題:這個版本在 Strict Mode 下有微妙的行為——React 18 的 Strict Mode 會 double-invoke effect,導致 ref 被更新兩次。雖然在 production 不影響,但開發時可能造成困惑。

方式二:useRef 直接同步更新

既然 useEffect 有時機問題,何不直接在 render 階段更新?

unknown node

這個版本透過兩個 ref 來追蹤——一個存「現在的」,一個存「上一次的」。當偵測到值改變時,把舊的搬到 prev,新的存進 curr。

問題:在 render 階段寫入 ref 可能在 Concurrent Mode 下造成問題,因為 render 可能被中斷重來。

方式三:useDistinctPrevious

有時候我們想要的不是「上一次 render 的值」,而是「上一次不同的值」。

unknown node

差別在於加入了 isEqual 比較函式。這在處理物件或陣列時特別有用——即使 reference 不同但內容相同,我們可能不想更新 previous 值。

方式四:useDeferredValue 的巧妙應用

React 18 的 useDeferredValue 原本是用於效能優化的——它會延遲更新非緊急的值。但有人發現它可以間接實現 usePrevious 的效果:

unknown node

嚴格來說這不是 usePrevious,但概念類似——你拿到了一個「稍微落後」的值,可以用來做過渡效果。

該用哪個?

  • 大部分情況:方式一(useRef + useEffect)就夠了
  • 需要精確比較:方式三(useDistinctPrevious)
  • 做過渡動畫:考慮 useDeferredValue
  • 避免在 render 階段寫入 ref,除非你清楚 Concurrent Mode 的影響

Related Posts