typescript - 在 ReactJS 中使用複雜物件的 useState 未如預期運作
我有一個函數組件,我正在為像這樣的複雜對象聲明一個 useState:
const [order, setOrder] = useState<IMasterState>({
DataInterface: null,
ErrorMsg: "",
IsRetrieving: true,
RetrievingMsg: "Fetching your order status..."
});
我現在嘗試透過在 useEffect 中呼叫 setOrder 來設定 order 的狀態,如下所示:
useEffect(() => {
(async function() {
let dh = new DataInterface("some string");
let errMsg = "";
// Get the sales order.
try
{
await dh.FetchOrder();
}
catch(error: any)
{
errMsg = error;
};
setOrder(salesOrder => ({...salesOrder, IsRetrieving: false, ErrorMsg: errMsg, DataInterface: dh}));
})();
}, []);
照目前的情況看,這似乎運作正常。然而,我有一個 setInterval 物件會在 order.IsRetrieving 為 true 時更改螢幕訊息:
const [fetchCheckerCounter, setFetchCheckerCount] = useState<number>(0);
const statusFetcherWatcher = setInterval(() => {
if (order.IsRetrieving)
{
if (fetchCheckerCounter === 1)
{
setOrder(salesOrder => ({...salesOrder, RetrievingMsg: "This can take a few seconds..."}));
}
else if (fetchCheckerCounter === 2)
{
setOrder(salesOrder => ({...salesOrder, RetrievingMsg: "Almost there!.."}));
}
setFetchCheckerCount(fetchCheckerCounter + 1);
}
else
{
// Remove timer.
clearInterval(statusFetcherWatcher);
}
}, 7000);
問題是,對於那段程式碼區塊,order.IsRetrieving 總是 true,即使它確實會變成 false,而我的網站也會相應地變化,甚至顯示來自 dh.FetchOrder() 的資料。這意味著我的計時器在背景中會進入無限循環。
那麼我設定 order 的狀態正確嗎?在網上很難找到明確的答案,因為所有的答案幾乎都是關於向陣列中添加新項目。
答案#1
問題
- 你正在將間隔設置為函數體中的一個無意的副作用。
- 你在間隔回呼中閉包了初始的
order.isRetreiving狀態值。
解決方案
使用掛載的 useEffect 來啟動間隔,並使用 React ref 在狀態更新時緩存該值,以便在非同步回調中可以訪問當前值。
const [order, setOrder] = useState<IMasterState>({
DataInterface: null,
ErrorMsg: "",
IsRetrieving: true,
RetrievingMsg: "Fetching your order status..."
});
const orderRef = useRef(order);
useEffect(() => {
orderRef.current = order;
}, [order]);
useEffect(() => {
const statusFetcherWatcher = setInterval(() => {
if (orderRef.current.IsRetrieving) {
if (fetchCheckerCounter === 1) {
setOrder(salesOrder => ({
...salesOrder,
RetrievingMsg: "This can take a few seconds...",
}));
} else if (fetchCheckerCounter === 2) {
setOrder(salesOrder => ({
...salesOrder,
RetrievingMsg: "Almost there!..",
}));
}
setFetchCheckerCount(counter => counter + 1);
} else {
// Remove timer.
clearInterval(statusFetcherWatcher);
}
}, 7000);
return () => clearInterval(statusFetcherWatcher);
}, []);
