在React中,我应该在哪里使用hooks进行API调用?

81

基本上,我们在React类组件中使用componentDidMount()生命周期方法进行API调用,就像下面这样

     componentDidMount(){
          //Here we do API call and do setState accordingly
     }
在React v16.7.0引入钩子之后,它们大多类似于功能组件。
我的问题是,在使用hooks的功能组件中,我们应该在哪里进行API调用?
我们是否有类似于componentDidMount()的方法来实现这一点?
5个回答

147

2023更新:在服务器上加载数据或使用React Query

新编写的官方文档提供了一个如何使用useEffect获取数据而不会出现竞争条件问题的示例

如果您正在使用像Next.js或Remix这样的元框架,它们允许在服务器上获取数据并将数据作为props传递给组件,这可以消除在客户端获取数据的需要。这往往对UX和性能更好,因为它消除了传统的渲染后获取数据模型,或者在转换时预取下一页所需的数据。

如果您想要一个纯客户端解决方案,并且与Suspense配合得很好:

  • React Query是React最流行的声明式数据获取库,提供有用的功能,如缓存、重新获取、预取等。
  • SWR是一个更轻量级的选项。

2021 更新:Suspense

正如 Dan Abramov 在 this GitHub Issue 中所说:

从长远来看,我们将不再鼓励使用 useEffect 模式,因为它会导致竞争条件。例如 - 在您的调用开始和结束之间,任何事情都可能发生,并且您可能已经获得了新的 props。相反,我们将推荐使用 Suspense 进行数据获取。


是的,使用钩子函数有一个类似但不完全相同的替代方法可以替换componentDidMount,那就是useEffect钩子函数。

其他答案并没有真正回答你关于在哪里进行API调用的问题。你可以通过使用useEffect将空数组或对象作为第二个参数传入来替换componentDidMount()进行API调用。关键在于第二个参数。如果你不提供空数组或对象作为第二个参数,API调用将在每次渲染时被调用,并且它实际上变成了一个componentDidUpdate

如文档中所述:

传递一个空数组[]作为输入告诉React你的effect不依赖于组件的任何值,因此该effect仅在挂载时运行并在卸载时清除;它不会在更新时运行。

以下是一些需要进行API调用的场景示例:

仅在挂载时进行API调用

尝试运行下面的代码并查看结果。

function User() {
  const [firstName, setFirstName] = React.useState(null);
  const [lastName, setLastName] = React.useState(null);
  
  React.useEffect(() => {
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        const {name} = data.results[0];
        setFirstName(name.first);
        setLastName(name.last);
      });
  }, []); // <-- Have to pass in [] here!

  return (
    <div>
      Name: {!firstName || !lastName ? 'Loading...' : `${firstName} ${lastName}`}
    </div>
  );
}

ReactDOM.render(<User />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

每当某个属性/状态改变时调用API

例如,如果您正在显示一个用户的个人资料页面,其中每个页面都有一个userID状态/prop,您应该将该ID作为值传递到useEffect的第二个参数中,以便为新用户ID重新获取数据。componentDidMount在这里不够用,因为如果您直接从用户A转到用户B的个人资料,则组件可能不需要重新挂载。

在传统的类方式中,您可以执行以下操作:

componentDidMount() {
  this.fetchData();
}

componentDidUpdate(prevProps, prevState) {
  if (prevState.id !== this.state.id) {
    this.fetchData();
  }
}

使用钩子,代码如下:

useEffect(() => {
  this.fetchData();
}, [id]);

尝试运行下面的代码并查看结果。例如将ID更改为2,以查看useEffect再次运行。

function Todo() {
  const [todo, setTodo] = React.useState(null);
  const [id, setId] = React.useState(1);
  
  React.useEffect(() => {
    if (id == null || id === '') {
      return;
    }
    
    fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
      .then(results => results.json())
      .then(data => {
        setTodo(data);
      });
  }, [id]); // useEffect will trigger whenever id is different.

  return (
    <div>
      <input value={id} onChange={e => setId(e.target.value)}/>
      <br/>
      <pre>{JSON.stringify(todo, null, 2)}</pre>
    </div>
  );
}

ReactDOM.render(<Todo />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.8.1/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.1/umd/react-dom.development.js"></script>

<div id="app"></div>

你应该阅读关于useEffect的相关内容,以便了解它的使用限制和注意事项。

1
这个代码是可以工作的,但是如果你传递一个空数组到 useEffect 中,它会抱怨你在 useEffect 中使用的任何属性或方法没有在依赖数组中定义。"React Hook useEffect 缺少依赖项:'getData'。请将其包含在内或删除依赖项数组 react-hooks/exhaustive-deps" - Organic
@Organic 然�包括�赖项?这里的示例代�没有使用自定义函数调用API,因此�需��赖项。感谢您的投票�。 - Yangshun Tay
1
如果你在数组中包含依赖项,它不仅会运行一次。它会运行多次,有时会进入无限循环。钩子比解决问题更多。 - Organic
1
这曾经是一个很棒的回答,但是丹在末尾所做的预言已经过去了很长时间。我现在仍在研究在React中进行API调用的最佳方式,并发现它非常困难,但显而易见的是,每个人都说我们现在不应该将API调用放在 useEffect() 中。 - Aaron Beall
@YangshunTay 谢谢,我会去看看那些框架!目前还没有使用任何一个。 - Aaron Beall
显示剩余6条评论

13

我只是发布这篇文章作为更简单的理解方式,根据我的努力。感谢Yangshun Tay的帖子,它几乎涵盖了所有内容。

组件挂载时的API调用

代码:

  useEffect(() => { 
    // here is where you make API call(s) or any side effects
    fetchData('/data')
  }, [] ) /** passing empty brackets is necessary */

因此,使用带有空参数[]useEffect(fn,[])会使fn()在组件创建(挂载)和销毁(卸载)时触发一次,而不依赖于任何值。

专业提示:

如果在这个fn中有return(),那么它将像类组件的componentWillUnmount()生命周期一样起作用。

  useEffect(() => { 
   fetchData('/data')
   return () => {
    // this will be performed when component will unmount
    resetData()
   }
  }, [] )

当某个值发生变化时调用API

如果你想要在某个值发生变化时调用API,只需将存储该值的变量传递到useEffect()的参数数组中即可。

 useEffect(() => {
  // perform your API call here
  updateDetails();
 },[prop.name]) /** --> will be triggered whenever value of prop.name changes */

这将确保每当prop.name的值发生更改时,您的钩子函数将被触发。

还要注意:当组件挂载时,此钩子函数也将被调用。因此,在那时,您的名称值可能处于初始状态,这在您的视图中是不太合意的。因此,您可以在函数中添加自定义条件以避免不必要的API调用。


1
投票支持这篇文章,因为最后提供了重要的注意事项:“...所以您可以在函数中添加自定义条件,以避免不必要的API调用。” - Glenn Mohammad

10
你可以使用一个为你提供钩子的库,比如https://dataclient.io
然后获取你的数据就变得非常简单了:
const todo = useSuspense(TodoResource.get, { id });

function Todo({id}) {
  const todo = useSuspense(TodoResource.get, { id });

  return <pre>{JSON.stringify(todo, null, 2)}</pre>;
}

const TodoResource = Rest.createResource({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  schema: Rest.schema.Entity(class Todo {}),
});

var { CacheProvider, AsyncBoundary, useSuspense } = RDC;

function App() {
  const [id, setId] = React.useState(1);

  return (
   <div>
    <input value={id} onChange={e => setId(e.target.value)}/>
    <br/>
    <AsyncBoundary><Todo id={id} /></AsyncBoundary>
   </div>
  );
}
ReactDOM.createRoot(document.querySelector('#app')).render(<CacheProvider><App/></CacheProvider>);
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@data-client/react@0.9.4/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@data-client/rest@0.9.4/dist/index.umd.min.js"></script>

<div id="app"></div>

现在你通过id获取了这篇文章。所有非正常情况(加载、错误状态)都由Suspense和Error boundaries处理。
任何变动都是原子且立即生效的
要开始使用,请按照这个简单的指南:https://dataclient.io/docs/getting-started/installation 仅有9kb的压缩文件大小,这将为你节省很多麻烦,并且从长远来看,由于减少了重复的代码,还能降低你的捆绑文件大小。 演示

3

当您使用hooks API的功能组件时,可以使用useEffect()方法产生副作用。每当由于这些副作用而更新状态时,组件将重新渲染。

文档示例。

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

例如,您可以在异步请求的回调函数中调用setCount。当回调执行时,状态将得到更新,React将重新渲染组件。另外从文档中可以看到:

提示

如果您熟悉React类生命周期方法,可以将useEffect Hook视为componentDidMountcomponentDidUpdatecomponentWillUnmount的组合。


2
你也可以使用use-http,例如:
import useFetch from 'use-http'

function App() {
  // add whatever other options you would add to `fetch` such as headers
  const options = {
    method: 'POST',
    body: {}, // whatever data you want to send
  }

  var [data, loading, error] = useFetch('https://example.com', options)

  // want to use object destructuring? You can do that too
  var { data, loading, error } = useFetch('https://example.com', options)

  if (error) {
    return 'Error!'
  }

  if (loading) {
    return 'Loading!'
  }

  return (
    <code>
      <pre>{data}</pre>
    </code>
  )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接