6

我有一个用例,页面必须在第一次渲染和单击按钮时调用相同的 fetch 函数。

代码类似于以下代码(参考:https ://stackblitz.com/edit/stackoverflow-question-bink-62951987?file=index.tsx ):

import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';

const App: FunctionComponent = () => {
  const [selected, setSelected] = useState<string>('a');
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<boolean>(false);
  const [data, setData] = useState<string | undefined>(undefined);

  const query = useCallback(async () => {
    setLoading(true)

    try {
      const res = await fetchBackend(selected);
      setData(res);
      setError(false);
    } catch (e) {
      setError(true);
    } finally {
      setLoading(false);
    }
  }, [])

  useEffect(() => {
    query();
  }, [query])

  return (
    <div>
      <select onChange={e => setSelected(e.target.value)} value={selected}>
        <option value="a">a</option>
        <option value="b">b</option>
      </select>
      <div>
        <button onClick={query}>Query</button>
      </div>
      <br />
      {loading ? <div>Loading</div> : <div>{data}</div>}
      {error && <div>Error</div>}
    </div>
  )
}

export default App;

对我来说,问题是 fetch 函数总是在任何输入更改时触发,因为eslint-plugin-react-hooks强制我在useCallback钩子中声明所有依赖项(例如:选定状态)。我必须useCallback使用它才能与useEffect.

我知道我可以将函数放在组件之外并传递所有参数(props、setLoading、setError、..等)以使其正常工作,但我想知道是否可以在保留相同效果的同时存档组件内部的 fetch 函数并遵守eslint-plugin-react-hooks?


[更新] 对于有兴趣查看工作示例的任何人。这是从接受的答案派生的更新代码。 https://stackblitz.com/edit/stackoverflow-question-bink-62951987-vxqtwm?file=index.tsx

4

3 回答 3

4

像往常一样添加所有依赖项useCallback,但不要在 useEffect 中创建另一个函数:

useEffect(query, [])

对于异步回调(如您的情况下的查询),您需要使用老式的 promise 方式 with.then和回调.catch.finally以便将 void 函数传递给useCallback,这是useEffect.

另一种方法可以在React 的文档中找到,但根据文档不推荐。

毕竟,传递给的内联函数useEffect无论如何都会在每次重新渲染时重新声明。使用第一种方法,您将仅在查询的部门发生变化时传递新函数。警告也应该消失。;)

于 2020-07-17T10:31:01.933 回答
1

有一些模型可以实现某些功能,您需要在组件安装单击按钮/其他时调用fetch 函数。在这里,我为您带来另一个模型,您可以通过仅使用钩子来实现,而无需直接基于按钮单击调用fetch 函数。它还将帮助您满足hook deps 数组的 eslint 规则,并轻松确保无限循环的安全。实际上,这将利用 effect hook called和 other being的力量。但是如果你有多个函数来获取不同的数据,那么你可以考虑很多选项,比如useReduceruseEffectuseState方法。好吧,看看这个项目,我试图在其中实现与您想要的类似的东西。

https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js

让我们稍微谈谈模型

export default function App() {
  const [data, setDate] = React.useState("");
  const [id, setId] = React.useState(1);
  const [url, setUrl] = React.useState(
    `https://jsonplaceholder.typicode.com/todos/${id}`
  );
  const [isLoading, setIsLoading] = React.useState(false);

  React.useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(json => {
        setDate(json);
        setIsLoading(false);
      });
  }, [url]);

  return (
    <div className="App">
      <h1>Fetch data from API in React Hooks</h1>
      <input value={id} type="number" onChange={e => setId(e.target.value)} />
      <button
        onClick={() => {
          setIsLoading(true);
          setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
        }}
      >
        GO & FETCH
      </button>
      {isLoading ? (
        <p>Loading</p>
      ) : (
        <pre>
          <code>{JSON.stringify(data, null, 2)}</code>
        </pre>
      )}
    </div>
  );
}

在这里,我使用初始链接在第一次渲染中获取数据,并且在每个按钮单击而不是调用任何方法时,我更新了效果挂钩的 deps 数组中存在的状态useEffect,以便useEffect再次运行。

于 2020-07-17T14:29:58.693 回答
0

我认为您可以轻松实现所需的行为

useEffect(() => {
    query();
  }, [data]) // Only re-run the effect if data changes

有关详细信息,请导航到此官方文档页面的末尾。

于 2020-07-17T10:44:02.320 回答