使用React钩子强制触发异步请求

时间:2019-04-17 11:45:50

标签: reactjs react-hooks

例如,在单击按钮时,我很难决定如何强制地 触发API调用。
我不确定采用钩子的正确方法是什么,因为似乎有不止一种方法,但是我不知道哪种方法是“最佳”方法以及最终的含义。

我发现以下示例非常简单,可以满足我的要求:

使用useEffect()和触发值

function SomeFunctionComponent() {
  const [fakeData, setFakeData] = useState(0);
  const [trigger, setTrigger] = useState(false);

  async function fetchData() {
    if (!trigger) return;

    const newData = await someAPI.fetch();

    setTrigger(false);
    setFakeData(newData);
  }

  useEffect(() => {
    fetchData();
  }, [trigger]);

  return (
    <React.Fragment>
      <p>{fakeData}</p>
      <button onClick={() => setTrigger(!trigger)}>Refresh</button>
    </React.Fragment>
  );
}

Example

只需调用API,然后调用setState()

function SomeFunctionComponent() {
  const [fakeData, setFakeData] = useState(0);

  async function fetchData() {
    const newData = await someAPI.fetch();

    setFakeData(newData);
  }

  return (
    <React.Fragment>
      <p>{fakeData}</p>
      <button onClick={fetchData}>Refresh</button>
    </React.Fragment>
  );
}

Example

还有其他利用useCallback()的方法,但是据我所知,它们在避免向下传递回调时避免重新渲染子组件很有用,它等效于第二个示例。

我认为useEffect方法仅在必须在组件安装上以编程方式运行某些东西时才有用,但实际上具有触发副作用的虚拟值看起来很冗长。
仅仅调用函数看起来就足够实用和简单了,但是我不确定在渲染过程中是否允许函数组件执行副作用。

哪种方法最常见,最正确的方法是在React中使用钩子进行命令式调用?

2 个答案:

答案 0 :(得分:3)

当我试图找出最好的书写方式时,我要做的第一件事就是看我如何使用它。对于您的情况,此代码:

    <React.Fragment>
      <p>{fakeData}</p>
      <button onClick={fetchData}>Refresh</button>
    </React.Fragment>

似乎是最直接,最简单的。 <button onClick={() => setTrigger(!trigger)}>Refresh</button>之类的东西掩盖了您的实现细节。

关于您的问题,请注意“我不确定在渲染过程中是否允许功能组件执行副作用”。 ,该功能组件在渲染期间不会产生副作用,因为当您单击按钮时不会发生渲染。只有当您调用setFakeData时,渲染才会真正发生。在这方面,实现1和实现2之间没有实际区别,因为只有在您调用setFakeData时,渲染才会发生。

开始进一步推广时,您可能会希望将此实现全部更改为更通用的内容,例如:

  function useApi(action,initial){
    const [data,setData] = useState({
      value:initial,
      loading:false
    });

    async function doLoad(...args){
        setData({
           value:data.value,
           loading:true
        });
        const res = await action(...args);
        setData({
            value:res,
            loading:false
        })
    }
    return [data.value,doLoad,data.loading]
  }
  function SomeFunctionComponent() {
    const [data,doLoad,loading] = useApi(someAPI.fetch,0)
    return <React.Fragment>
      <p>{data}</p>
      <button onClick={doLoad}>Refresh</button>
    </React.Fragment>
  }

答案 1 :(得分:3)

接受的答案实际上确实破坏了rules of hooks。由于单击是 Asynchronous ,这意味着在获取调用期间可能会发生其他渲染,这将创建 SideEffects ,并可能创建可怕的 Invalid Hook Call Warning

我们可以通过在调用setState()函数之前检查组件是否已安装来对其进行修复。下面是我的解决方案,它很容易使用。

挂钩功能

function useApi(actionAsync, initialResult) {
  const [loading, setLoading] = React.useState(false);
  const [result, setResult] = React.useState(initialResult);
  const [fetchFlag, setFetchFlag] = React.useState(0);

  React.useEffect(() => {
    if (fetchFlag == 0) {
      // Run only after triggerFetch is called 
      return;
    }
    let mounted = true;
    setLoading(true);
    actionAsync().then(res => {
      if (mounted) {
        // Only modify state if component is still mounted
        setLoading(false);
        setResult(res);
      }
    })
    // Signal that compnoent has been 'cleaned up'
    return () => mounted = false;
  }, [fetchFlag])

  function triggerFetch() {
    // Set fetchFlag to indirectly trigger the useEffect above
    setFetchFlag(Math.random());
  }
  return [result, triggerFetch, loading];
}

在React挂钩中的使用

function MyComponent() {
  async function fetchUsers() {
    const data = await fetch("myapi").then((r) => r.json());
    return data;
  }

  const [fetchResult, fetchTrigger, fetchLoading] = useApi(fetchUsers, null);

  return (
    <div>
      <button onClick={fetchTrigger}>Refresh Users</button>
      <p>{fetchLoading ? "Is Loading" : "Done"}</p>
      <pre>{JSON.stringify(fetchResult)}</pre>
    </div>
  );
}