前言
Hooks出來已經有段時間了,相信大家都用過段時間了,有沒有小伙伴們遇到坑呢,我這邊就有個 setInterval 的坑,和小伙伴們分享下解決方案。
前言
寫個 count 每秒自增的定時器,如下寫法結果,界面上 count 為 1 ?
function Counter() { let [count, setCount] = useState(0); useEffect(() => { let id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
https://codesandbox.io/embed/hooks-setinterval-error-w4qu6
如果某些特定值在兩次重渲染之間沒有發生變化,你可以通知 react 跳過對 effect 的調用。就是將第二個參數改成 [] ,類似於更接近類組件的 componentDidMount 和 componentWillUnmount 生命周期,只執行一次。 effect 的第二個參數中傳入的值就是 它更改的話, effect 也會重新執行一遍的值。
因為 Effect 的第二個參數為 [] ,沒有依賴, Effect 只會執行一次。 setInterval 中拿到的count 永遠是 0 ,界面會一直顯示 1 ,如下所示:
function Counter() { let [count, setCount] = useState(0); useEffect(() => { let id = setInterval(() => { setCount(0 + 1); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
那有些小伙伴會說,如果我們直接往第二個參數加 count 呢
function Counter() { //... useEffect(() => { let id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]); //... }
這樣效果是對的,但是性能不好。每當 count 更改了, useEffect 就會渲染一次,定時器也會不停的被新增與移除。如下所示:
//第一次 function Counter() { //... useEffect(() => { let id = setInterval(() => { setCount(0 + 1); }, 1000); return () => clearInterval(id); }, [0]); //... } //第二次 function Counter() { //... useEffect(() => { let id = setInterval(() => { setCount(1 + 1); }, 1000); return () => clearInterval(id); }, [1]); //... //第N次 }
那到底要怎么做才能有保障性能,定時器只監聽一次,又使定時器起作用呢?
方案一、函數式更新
useState 中的set方法可接收函數,該函數將接收先前的 state ,並返回一個更新后的值。這樣定時器每次拿到的是最新的值。
function Counter() { let [count, setCount] = useState(0); useEffect(() => { let id = setInterval(() => { setCount(v => { return v + 1; }); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
https://codesandbox.io/embed/hooks-setinterval-usestate-grres
方案二、用useRef
useRef 返回一個可變的 ref 對象,返回的 ref 對象在組件的整個生命周期內保持不變。
將定時器函數提取出來,每次定時器觸發時,都能取到最新到 count .
function Counter() { let [count, setCount] = useState(0); const myRef = useRef(null); myRef.current = () => { setCount(count + 1); }; useEffect(() => { let id = setInterval(() => { myRef.current(); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
https://codesandbox.io/embed/hooks-setinterval-useref-cgif3
思考:為什么不直接 setInterval(myRef.current, 1000) 這樣寫不行呢,還要包個方法返回?
function Counter() { let [count, setCount] = useState(0); const myRef = useRef(null); myRef.current = () => { setCount(count + 1); }; useEffect(() => { let id = setInterval(myRef.current, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
https://codesandbox.io/embed/hooks-setinterval-useref-error-52dm0
下面的例子可以很好的解釋。假如把 myRef.current 為 cur 變量,定時器的第一個參數為 interval 變量, cur 變量更改, interval 的取的還是之前賦值的值。
var cur=()=>{var count=0;console.log(count)}; var interval=cur; var cur=()=>{var count=1;console.log(count)}; interval();//0 var cur=()=>{var count=0;console.log(count)}; var interval=()=>{cur()}; var cur=()=>{var count=1;console.log(count)}; interval();//1
方案三、自定義hook
可以寫個自定義 hook ,方便重復使用。
function useInterval(fun) { const myRef = useRef(null); useEffect(() => { myRef.current = fun; }, [fun]); useEffect(() => { let id = setInterval(() => { myRef.current(); }, 1000); return () => clearInterval(id); }, []); } function Counter() { let [count, setCount] = useState(0); useInterval(() => { setCount(count + 1); }); return <h1>{count}</h1>; }
https://codesandbox.io/embed/hooks-setinterval-ownhooks-0tpxe
佛山vi設計https://www.houdianzi.com/fsvi/ 豌豆資源搜索大全https://55wd.com
方案四、用useReducer
將 count 變量存入 reducer 中,使用 useReducer 更新 count
function reducer(state, action) { switch (action.type) { case "increment": return state + 1; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, 0); useEffect(() => { setInterval(() => { dispatch({ type: "increment" }); }, 1000); }, []); return <h1>{state}</h1>; }