使用react-hooks执行状态更新时执行异步代码

时间:2018-12-22 19:43:40

标签: javascript reactjs react-hooks

我有类似的东西:

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

...

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

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

setLoading()似乎不接受像以前的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));
  };

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

5 个答案:

答案 0 :(得分:7)

test2设置器在状态更新完成后不提供回调,就像React类组件中的setState一样。为了复制相同的行为,您可以使用Hooks在useState中使用类似模式的React类组件中的componentDidUpdate生命周期方法

useEffect挂钩将第二个参数作为值的数组,在渲染周期完成后,React需要监视这些值以进行更改。

useEffect

编辑

const [loading, setLoading] = useState(false); ... useEffect(() => { doSomething(); // This is be executed when `loading` state changes }, [loading]) setLoading(true); 不同,setState钩子的更新程序没有回调,但是您始终可以使用useState复制上述行为。但是,您需要确定加载变化

代码的功能方法如下

useEffect

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

答案 1 :(得分:6)

等待直到重新渲染组件。

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>
    </>
  );
}

答案 2 :(得分:0)

我对此有一个建议。

您可以使用 React Ref 来存储状态变量的状态。然后使用 react ref 更新状态变量。这将呈现页面刷新,然后在异步函数中使用 React Ref。

const stateRef = React.useRef().current
const [state,setState] = useState(stateRef);

async function some() {
  stateRef = { some: 'value' }
  setState(stateRef) // Triggers re-render
  
  await some2();
}

async function some2() {
  await someHTTPFunctionCall(stateRef.some)
  stateRef = null;
  setState(stateRef) // Triggers re-render
}

答案 3 :(得分:0)

创建了一个自定义 bankScheme = bankScheme ? bankScheme : _.get(j, 'PaymentMeans') === '58' ? '58Found' : '58NotFound'; 钩子,其工作方式类似于普通的 useState 钩子,不同之处在于此自定义钩子的状态更新器函数需要一个回调,该回调将在状态更新和组件重新渲染后执行。

打字稿解决方案

useState

Javascript 解决方案

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];
}

答案 4 :(得分:-1)

我不确定这里的用例是什么,但是我认为我们可以通过创建自定义钩子来实现异步操作。

以下仅供参考

const useAPI =(initState)=>{
    const [custState,changeCustState] = useState(initState);
    return ([custState,(newState,accept a callback here )=>{
    //console.log(newState);
    /// execute the call back ()
    return changeCustState(newState);}]);
};

...

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