当前位置: 首页 > 知识库问答 >
问题:

使用react钩子更新状态时执行异步代码

曾苗宣
2023-03-14

我有点像:

const [loading, setLoading] = useState(false);

...

setLoading(true);
doSomething(); // <--- when here, loading is still false. 

设置状态仍然是异步,那么等待这个setLoding()调用完成的最佳方法是什么?

setLoding()似乎不像setState()那样接受回调。

getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (this.state.pagesSeen.includes(this.state.page + 1)) {
      return this.setState({
        page: this.state.page + 1,
      });
    }

    if (this.state.prefetchedOrders) {
      const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
      return this.setState({
        orders: allOrders,
        page: this.state.page + 1,
        pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
        prefetchedOrders: null,
      });
    }

    this.setState(
      {
        isLoading: true,
      },
      () => {
        getOrders({
          page: this.state.page + 1,
          query: this.state.query,
          held: this.state.holdMode,
          statuses: filterMap[this.state.filterBy],
        })
          .then((o) => {
            const { orders } = o.data;
            const allOrders = this.state.orders.concat(orders);
            this.setState({
              orders: allOrders,
              isLoading: false,
              page: this.state.page + 1,
              pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
              // Just in case we're in the middle of a prefetch.
              prefetchedOrders: null,
            });
          })
          .catch(e => console.error(e.message));
      },
    );
  };
  const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);

    getOrders({
      page: page + 1,
      query: localQuery,
      held: localHoldMode,
      statuses: filterMap[filterBy],
    })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
  };

在上面,我们希望按顺序运行每个setwhater调用。这是否意味着我们需要设置许多不同的useEffect挂钩来复制这种行为?

共有3个答案

郎俊雅
2023-03-14

创建了一个自定义的useState钩子,其工作原理类似于正常的useState钩子,除了此自定义钩子的状态更新器函数需要一个回调,该回调将在状态更新和组件重渲染后执行。

打字解决方案

import { useEffect, useRef, useState } from 'react';

type OnUpdateCallback<T> = (s: T) => void;
type SetStateUpdaterCallback<T> = (s: T) => T;
type SetStateAction<T> = (newState: T | SetStateUpdaterCallback<T>, callback?: OnUpdateCallback<T>) => void;

export function useCustomState<T>(init: T): [T, SetStateAction<T>];
export function useCustomState<T = undefined>(init?: T): [T | undefined, SetStateAction<T | undefined>];
export function useCustomState<T>(init: T): [T, SetStateAction<T>] {
    const [state, setState] = useState<T>(init);
    const cbRef = useRef<OnUpdateCallback<T>>();

    const setCustomState: SetStateAction<T> = (newState, callback?): void => {
        cbRef.current = callback;
        setState(newState);
    };

    useEffect(() => {
        if (cbRef.current) {
            cbRef.current(state);
        }
        cbRef.current = undefined;
    }, [state]);

    return [state, setCustomState];
}

Javascript解决方案

import { useEffect, useRef, useState } from 'react';

export function useCustomState(init) {
    const [state, setState] = useState(init);
    const cbRef = useRef();

    const setCustomState = (newState, callback) => {
        cbRef.current = callback;
        setState(newState);
    };

    useEffect(() => {
        if (cbRef.current) {
            cbRef.current(state);
        }
        cbRef.current = undefined;
    }, [state]);

    return [state, setCustomState];
}

用法

const [state, setState] = useCustomState(myInitialValue);
...
setState(myNewValueOrStateUpdaterCallback, () => {
   // Function called after state update and component rerender
})
卫宏硕
2023-03-14

等待,直到你的组件重新渲染。

const [loading, setLoading] = useState(false);

useEffect(() => {
    if (loading) {
        doSomething();
    }
}, [loading]);

setLoading(true);

您可以通过以下方式提高清晰度:

function doSomething() {
  // your side effects
  // return () => {  }
}

function useEffectIf(condition, fn) {
  useEffect(() => condition && fn(), [condition])
}

function App() {
  const [loading, setLoading] = useState(false);
  useEffectIf(loading, doSomething)

  return (
    <>
      <div>{loading}</div>
      <button onClick={() => setLoading(true)}>Click Me</button>
    </>
  );
}
符畅
2023-03-14

在完成状态更新后,setter不会像React类组件中的setState那样提供回调。为了复制相同的行为,您可以在React类组件中使用类似的模式,如使用Hooks使用use效应组件diUpdate生命周期方法

useffecthooks将第二个参数作为一个值数组,在渲染周期完成后,该数组需要监控更改。

const [loading, setLoading] = useState(false);

...

useEffect(() => {
    doSomething(); // This is be executed when `loading` state changes
}, [loading])
setLoading(true);

编辑

setState不同,useState钩子的更新程序没有回调,但是您可以始终使用use效应来复制上述行为。但是您需要确定加载更改

代码的函数方法如下所示

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
const prevLoading = usePrevious(isLoading);

useEffect(() => {
   if (!prevLoading && isLoading) {
       getOrders({
          page: page + 1,
          query: localQuery,
          held: localHoldMode,
          statuses: filterMap[filterBy],
      })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
   }
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);

const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);
  };
 类似资料:
  • 问题内容: 我有类似的东西: 设置状态仍然是异步的,那么等待此调用完成的最佳方法是什么? 本似乎并没有接受一个回调像使用。 一个例子 基于类 转换为基于功能的 在上面,我们要顺序运行每个setWhatever调用。这是否意味着我们需要设置许多不同的useEffect挂钩来复制此行为? 问题答案: 状态更新完成后,setter不会提供回调,就像React类组件中的setState一样。为了复制相同的

  • 它几乎添加了一个对象,该对象包含来自主窗体的子窗体的值。 这是我用作按钮的函数的函数。 这将一个新对象添加到一个名为的状态,该状态是一个对象数组。 提前谢了。

  • 问题内容: 我正在尝试新的React Hooks,并有一个带有计数器的Clock组件,该计数器应该每秒增加一次。但是,该值不会增加到超过一。 问题答案: 原因是传递给的闭包中的回调仅访问第一个渲染器中的变量,而无法访问后续渲染器中的新值,因为第二次未调用。 回调中的值始终为0 。 就像您熟悉的状态挂钩一样,状态挂钩有两种形式:一种是处于更新状态的状态,另一种是将当前状态传入的回调形式。您应该使用第

  • 我正在试用新的反应挂钩,并有一个时钟组件,计数器应该每秒钟增加一次。但是,该值不增加超过1。

  • 我正在React中使用useState钩子,状态将不会在

  • 我正在尝试使用状态将一个表单输入值复制到另一个表单输入值,但状态没有更新 我创建了一个带有按钮的表单,其中左侧表单输入的值应复制到右侧表单输入。这是我的代码: 无论我做什么,dbSecName的状态都不会改变。我试着设置一个新的常量。我尝试使onclick函数异步并等待。如果我为我试图设置的变量的值添加,我可以正确地看到它,但是dbSecName的console.log总是显示原始值。 我真的不知