這是主包在面試中遇到的一道題目,面試官的問題是:"這個頁面初次展示出來時Count和step的值是什么,我點擊按鈕count和step的值有什么變化?“
這個題目主包回答的不好,所以想做一個總結。
題目
import React, { useState, useEffect } from 'react';function useInterval(callback, delay) {useEffect(() => {const id = setInterval(() => {console.log('[Timer Tick]的 count:', callback._countSnapshot);callback();}, delay);return () => clearInterval(id);}, [delay]);
}export default function BuggyCounter() {const [count, setCount] = useState(0);const [step, setStep] = useState(1);function tick() {setCount(count + step);}// 注冊定時器useInterval(tick, 1000);return (<div><h1>Count: {count}</h1><div><button onClick={() => setStep((s) => s + 1)}>Increase Step</button><span> 當前 step: {step} </span></div></div>);
}
分析?
首先,先好好閱讀代碼,主包現在發現主包在面試中有一個致命的問題就是,很多問題還沒明白面試官的意思,或者像代碼,還沒明白代碼的意思就已經開始胡說了。往往是越說越遠,其實結束面試之后主包再次閱讀這段答的稀爛的代碼的時候,發現很能看懂。所以不要著急,先明白面試官 的意思在作答。
好的,我們來解析代碼,這個函數什么作用呢。首先我們要明白這是自定義了一個Hook。傳入兩個參數:callback是一個函數,delay動態參數用于設置定時器的。useEffect的部分就是以delay作為依賴,并在return這個處理副作用的部分進行一個清空計時器。它實現的功能是:安全的實行定時器,在組件卸載或?delay
?變化時,自動清除之前的定時器。
接下來看tick函數,就是去改變count的狀態為count+step;
調用定時器,每1000ms執行一次。
?
好的,現在解析完畢,開始作答。在剛進入頁面的時候,我們應該看的到一瞬間的count:0,step:1;但是1000ms后count變為1了。之后如果我們不點擊按鈕就會保持這樣一直不變。
問題
當我們點擊按鈕之后,只有step的值會增加,而count的值不會增加。
現在我們來解釋為什么會這樣:
首先,進入頁面之后,會因為初態的問題,count和step會保持初態0和1;但是1000ms后,定時器起效了此時tick函數起作用,但是它捕獲的是初態的count和step,在定時器循環的過程中,由于閉包問題,count和step一直沒有得到更新,所以count不會改變。
控制臺呢
你會發現callback函數中根本就沒有?_countSnapshot這個屬性,所以他是迷惑我們的,一直是undefined。
怎么更改
方案1
setCount((count)=> count + step);
這樣就好,我們在每一次更新時,通過回調獲取最新的count值。
方案2:將?tick
?加入?useInterval
?的依賴
修改?useInterval
,使其響應?callback
?的變化:
function useInterval(callback, delay) {useEffect(() => {const id = setInterval(callback, delay); // 直接使用最新的 callbackreturn () => clearInterval(id);}, [delay, callback]); // 依賴 callback
}
方案3:useRef
?
function useInterval(callback, delay) {const savedCallback = useRef();// 保存最新的回調useEffect(() => {savedCallback.current = callback;}, [callback]);// 設置定時器useEffect(() => {function tick() {savedCallback.current(); // 調用最新的回調}if (delay !== null) {const id = setInterval(tick, delay);return () => clearInterval(id);}}, [delay]);
}
“人間自有真情在 ,宜將寸心報春暉。”