網站搜尋

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

問題

  1. 你正在將間隔設置為函數體中的一個無意的副作用。
  2. 你在間隔回呼中閉包了初始的 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);
}, []);