【问题标题】:How to prevent useCallback from triggering when using with useEffect (and comply with eslint-plugin-react-hooks)?与 useEffect 一起使用时如何防止 useCallback 触发(并遵守 eslint-plugin-react-hooks)?
【发布时间】:2020-11-07 03:25:42
【问题描述】:

我有一个用例,页面必须在第一次渲染和单击按钮时调用相同的 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

【问题讨论】:

    标签: javascript reactjs typescript react-hooks eslint-plugin-react-hooks


    【解决方案1】:

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

    useEffect(query, [])

    对于异步回调(如您的情况下的查询),您需要使用带有 .then.catch.finally 回调的旧式承诺方式才能拥有传递给 useCallback 的 void 函数,这是 useEffect 所需要的。

    另一种方法可以在React's docs 上找到,但根据文档不推荐。

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

    【讨论】:

    • 感谢您向我展示了可能的方法。 useEffect(query, []) 对我来说看起来很棒,但是当我尝试将它与我的代码一起使用时,打字稿给了我一个错误(请参阅 imgur.com/a/tP0Lvp0.),因为 useEffect 不接受类型 '() => Promise '。
    • 是的,我没有注意到它是异步的。在这种情况下,如果您在 promise 上使用带有 .then.catch.finally 的旧式方法,则可以避免该错误。有了它,您将不再返回承诺。
    • 我在stackblitz.com/edit/stackoverflow-question-bink-vxqtwm 尝试了旧式方法。 But, when the select component changed, it still tr​​iggers re-render.我错过了什么吗?
    • 你没有用useEffect(query, [])替换效果你也不再需要查询函数前面的async关键字。
    • 对不起,我的错误。我已经更新了代码,它工作得很好。非常感谢!
    【解决方案2】:

    有一些模型可以在组件挂载点击时调用获取函数按钮/其他。在这里,我为您带来另一个模型,您可以通过仅使用 hooks 而无需基于按钮单击直接调用 fetch 函数 来实现两者。它还将帮助您满足 hook deps 数组的 eslint 规则 并轻松应对无限循环。实际上,这将利用名为useEffect 和其他useState 的效果挂钩的力量。但是如果你有多个函数来获取不同的数据,那么你可以考虑很多选项,比如 useReducer 方法。好吧,看看这个项目,我试图实现与您想要的类似的东西。

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

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

    【讨论】:

      【解决方案3】:

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

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

      有关详细信息,请导航到此 official docs 页面的末尾。

      【讨论】:

      • 感谢您的回复。我已经尝试过了,我认为这种方法会导致 fetch 函数被调用两次。第一次数据为空(尚未获取),第二次数据存在。
      • eslint 也抛出错误React Hook useEffect has a missing dependency: 'query'. Either include it or remove the dependency array.eslint(react-hooks/exhaustive-deps).
      猜你喜欢
      • 2020-11-01
      • 2020-02-15
      • 2020-08-02
      • 2019-11-26
      • 2019-09-17
      • 2021-06-06
      • 2021-08-17
      • 2020-10-08
      • 2023-03-24
      相关资源
      最近更新 更多