【问题标题】:Executing async code on update of state with react-hooks使用 react-hooks 更新状态时执行异步代码
【发布时间】:2019-05-22 18:05:39
【问题描述】:

我有类似的东西:

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 挂钩来复制这种行为?

【问题讨论】:

    标签: javascript reactjs callback react-hooks


    【解决方案1】:

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

    useEffect hooks 将第二个参数作为一个值数组,React 需要在渲染周期完成后监控这些值的变化。

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

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

    【讨论】:

    • 那么,在我们想要等待多个 setState 的情况下,我们需要多个 useEffect 钩子,对吧?
    • @Colin,如果您想在不同的状态发生变化时采取不同的行动,那么可以,但是如果您想对这些多个状态中的任何一个进行更改,那么您可以传递多个参数,例如 @ 987654331@
    • 是的,所以在上述情况下,您将拥有[isLoading, orders]
    • 但是要采取不同的行动,我们需要多个 useEffect 挂钩,对吧?另外,我们如何以这种方式执行顺序操作?更改不会同时触发两个钩子吗?
    • 顺序操作是什么意思。如果您使用所需的行为更新您的代码,如果我知道的话,我可以看看并提出解决方案
    【解决方案2】:

    等到您的组件重新渲染。

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

    【讨论】:

    • 嗯,所以我需要添加一个useEffect() 来监控每​​个状态以对其做出反应?
    • @Colin 您可以将 useEffect 视为 componentDidMount 或 componentDidUpdate 取决于上例中传递给回调的最后一个参数 [loading]
    • 说实话,我不认为这是一个好的答案;你能更好地解释一下doSomething 的真正作用吗?你的用例是什么?
    • 我认为现在这很有意义。这是一个人为的例子,但doSomething() 从 API 获取,如果有意义的话,需要知道 实际 当前状态。
    • @Colin 很好的解释,带有 hooks API 的例子egghead.io/lessons/react-use-the-usestate-react-hook
    【解决方案3】:

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

    Typescript 解决方案

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

    【讨论】:

      【解决方案4】:

      我对此有一个建议。

      您可以使用 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
      }
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-19
        • 2020-05-21
        • 2019-08-08
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        相关资源
        最近更新 更多