使用复杂对象的 useState 在 ReactJS 中无法按预期工作

Using useState of complex object not working as expected in ReactJS

我有一个功能组件,我正在为这样的复杂对象声明一个 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.IsRetrievingtrue:

    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. 您将间隔设置为函数体中的意外副作用。
  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);
}, []);