預先了解 setState 的兩種傳參方式
1、直接傳入新值 setState(options);
列如:
const [state, setState] = useState(0);
setState(state + 1);
2、傳入回調函數 setState(callBack);
例如:
const [state, setState] = useState(0);
setState((prevState) => prevState + 1); // prevState 是改變之前的 state 值,return 返回的值會作為新狀態覆蓋 state 值
useState 異步回調獲取不到最新值及解決方案
通常情況下 setState 直接使用上述第一種方式傳參即可,但在一些特殊情況下第一種方式會出現異常;
例如希望在異步回調或閉包中獲取最新狀態並設置狀態,此時第一種方式獲取的狀態不是實時的,React 官方文檔提到:組件內部的任何函數,包括事件處理函數和 Effect,都是從它被創建的那次渲染中被「看到」的,所以引用的值任然是舊的,最后導致 setState 出現異常:
import React, { useState, useEffect } from 'react';
const App = () => {
const [arr, setArr] = useState([0]);
useEffect(() => {
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve().then(() => {
setArr([...arr, 1]); // 此時賦值前 arr 為:[0]
})
.then(() => {
setArr([...arr, 2]); // 此時賦值前 arr 為舊狀態仍然為:[0]
});
}
return (
<>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
上面代碼,App 組件實際也是個閉包函數,handleClick 里面引用着 arr,第一次 setArr 后 arr 的值確實更新了,我們也可以在下面截圖中看到,但此次執行的 handleClick 事件處理函數作用域還是舊的,里面引用的 arr 仍然為舊的,導致第二次 setArr 后結果為 [0, 2]:
在 class 組件中我們可以使用 setState(options, callBack); 在 setState 的第二個參數回調函數中再次進行 setState,也不存在閉包作用域問題,但是 React Hook 中 useState 移除了 setState 的第二個參數,而且若嵌套太多也不佳;
解決方案1(推薦使用):
將上述代碼使用第二種(回調)方式傳參
const handleClick = () => {
Promise.resolve().then(() => {
setArr(prevState => [...prevState, 1]); // 這里也可以不改,使用第一中傳參方式 setArr([...arr, 1]); 因為這里不需要獲取最新狀態
})
.then(() => {
setArr(prevState => [...prevState, 2]); // 這里必須改成回調函數傳參方式,否則會讀取舊狀態,導致異常
});
}
解決方案2:
使用 useReducer 仿造類組件中的 forceUpdate 實現組件強制渲染;
import React, { useState, useReducer } from 'react';
const App = () => {
const [arr, setArr] = useState([0]);
const [, forceUpdate] = useReducer(x => x + 1, 0);
const handleClick = () => {
Promise.resolve().then(() => {
arr.push(1); // 如果這里也需要做一次渲染在改變狀態后調用 forceUpdate() 即可
})
.then(() => {
arr.push(2);
forceUpdate();
});
}
return (
<>
<h1>{arr.toString()}</h1>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
點擊前:
點擊后:
解決方案3:
利用 ref
import React, { useState, useRef, useEffect } from 'react';
const App = () => {
const [arr, setArr] = useState([0]);
let ref = useRef();
useEffect(() => {
ref.current = arr;
console.log(arr);
});
const handleClick = () => {
Promise.resolve().then(() => {
const now = [...ref.current, 1];
ref.current = now;
setArr(now);
})
.then(() => {
setArr([...ref.current, 2]);
});
}
return (
<>
<h1>{arr.toString()}</h1>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
最后附上官方相關文檔:為什么我會在我的函數中看到陳舊的 props 和 state ?