使用 useReducer 和 useCallback 解決 useEffect 依賴誠實與方法內置&外置問題


 

本文是在:https://juejin.im/post/5ceb36dd51882530be7b1585 的基礎上進行的探究,非常建議閱讀原文

一、useEffect 依賴誠實問題的粗暴解決及帶來的問題

之前的一個例子,在 useEffect 中直接執行 setInterval 導致依賴欺騙帶來的很多問題,詳細的內容請移步至:

const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => { console.log('render useEffect') const id = setInterval(() => { setCount(prevCount => prevCount + step); setStep(step => step + 1); console.log(`[] count is ${count}, step is ${step}`); }, 1000); return () => clearInterval(id); }, [step]);

上面代碼中,雖然通過 setStaet(prevState => prevState + 1) 這樣的方式取消對 count 的依賴,但是一旦代碼里面同時依賴了兩個 state,就無法通過這種方式解決。

上面的代碼中,最終解決的方案其實是在 useEffect 中依賴了 step,這已經是依賴誠實,但是造成的結果是顯而易見的:每次 step 的變動都會導致重新實例化一次 setInterval 。

13285-l0nv0p7q4ul.png

二、使用 useReducer 解決依賴誠實問題

我們最終的目的是 useEffect 本身的依賴只有 [ ],以為只有 [ ] 我們才能保證組件實例掛載的時候只會執行一次 setInterval

首先我們的依賴關系是發生在 useState 上的(具體的是 setCount),如果能夠解決 setCount 中本身對 count 和 step 的依賴關系是最好的。

而 react 的文檔中,明確提出了 useReducer 是 useState 的替代方案

文檔原文:

在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較復雜且包含多個子值,或者下一個 state 依賴於之前的 state 等

從這個介紹上來看,使用 useReducer 在上面的場景中是比 useState 更合適的。

useReducer hook 是 react 的內置 hook,在聲明上如下:

const [state, dispatch] = useReducer(reducer, initialArg, init);

useReducer 本身接受的參數有三個:

  • 一個 reducer:(state, action) => newStat(reducer 與 redux 中的概念其實一樣)
  • 初始化值
  • init 是用來進行惰性初始化的:init(initialArg)

如果使用 redux ,本身我們就不會直接操作 state,而是通過 dispatch 去觸發某些規則,因此 useReducer 本身也會返回一個 dispatch

1、聲明一個 reducer

下面的 reducer 比較簡單,處理了一下 increment

const reducer = (state, action) => { switch (action.type) { case 'increment': return { ...state, count: state.count + state.step, step: state.step + 1, } default: return state; } } 

2、使用 useReducer 聲明 state 和 dispatch

const initialState = { count: 0, step: 1 } const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state;

3、使用 dispatch 進行 state 的一些變更

一開始提出的代碼改成下面:

useEffect(() => { console.log('render useEffect') const id = setInterval(() => { dispatch({ type: 'increment' }); console.log(`[] count is ${state.count}, step is ${state.step}`); }, 1000); return () => clearInterval(id); }, []);

4、效果:

首先我們實現了實例一次 setInterval 定時器,但是卻能夠時刻處理 count 和 step 的變化

在一次渲染閉包內,能夠每次訪問到 state 的最新值

64154-uto9p0keipk.png

5、依賴真的都誠實了么?

現在對 count 和 step 的兩個依賴都剝離出去了,我們認為目前 useEffect 的依賴都是誠實的,其實不然。

因為我們最終還是依賴了 dispatch,不是只有 state 才叫依賴

但是我們都知道 dispatch 本身是不會變化的,因此我們認為對 uesEffect 來說,依賴都是誠實的

三、useCallback 解決 useEffect 內部函數的依賴誠實問題

1、非 useEffect 內部函數引起的依賴欺騙

上面代碼中我們發現,如果 dispatch 內部也依賴了某些變量,這個時候很容易造成依賴的欺騙問題。

為了解決這個問題,我們可能都需將其他函數寫在 useEffect 內部才能借助 eslint-plugin-react-hooks 這個插件檢查通過

可以針對思考下面代碼:

const [count, setCount] = useState(0); const [step, setStep] = useState(1); const setCountNew = () => { setCount(count + step); setStep(step + 1); } useEffect(() => { const tm = setInterval(() => { setCountNew(); console.log(count, step) }, 1000); return () => { clearInterval(tm); } }, []);

上面的代碼只是將 setCount 和 setStep 這樣的方法移到了 useEffect 外面,目前在 useEffect 中我們從代碼上看(忽略 console)是沒有 state 的依賴的,看起來是沒問題。

eslint 插件只會掃描出 setCountNew()

19773-3csl7ku5vdd.png

而上面的輸出結果只會輸出一次,即使我們有定時器。定時器是一致在執行的,但是頁面是不會變化的,因為每次在 setCountNew 的時候,拿到的 count 和 step 都是第一次渲染閉包的值,也就是 0 和 1

2、useCallback 解決依賴欺騙問題

有些情況下我們不能將函數都寫在 useEffect 內部,會造成無法管理,代碼也會臃腫。

useCallback 本身會返回一個方法,同時 useCallback 接收兩個參數:

  • 參數1:匿名方法,里面執行相關的邏輯
  • 參數2:數據依賴,本身 useCallback 需要監聽相關的依賴項,這些依賴項可以在上面的方法中使用

文檔的說明:

把內聯回調函數及依賴項數組作為參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新

上面方法的改造如下:

    const [count, setCount] = useState(0); const [step, setStep] = useState(1); const setCountNew = useCallback(() => { setCount(count + step); setStep(step + 1); }, [count, step]); useEffect(() => { console.log('render useEffect') const tm = setInterval(() => { setCountNew(); }, 1000); return () => { clearInterval(tm); } }, [setCountNew]);

上面的改動中,除了我們使用 useCallback 聲明一個 setCountNew 的方法,並且在 useEffect 方法本身用之外,useEffect 還依賴了 setCountNew

這個表示說明,當 setCountNew 發生變化的時候(本身如果 state 發生了變化則返回的方法也會發生變化)

輸出結果:

41524-yop3qbyh0lr.png

我們可以發現,輸出結果中,每次都會重新執行 useEffect ,因為對於 useEffect 來說,useCallback 的 memorize 回調已經發生變化,基於此,我們可以放心的認為 useEffect 中依賴都是誠實的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM