如何使用React Hooks来获取数据(附代码示例)

1,413 阅读4分钟

如果你还不熟悉从API中获取数据的过程或React Hooks,我建议查看这些主题的文章。

现在我们对钩子和数据检索的概念已经很熟悉了,我们可以讨论如何实现我们自己的自定义钩子,专门用于从外部API获取数据。

为什么要实现一个自定义React钩子来获取数据?

与一般的自定义钩子一样,使用它们的主要好处是:

  • 可读性
  • 封装副作用的能力
  • 在多个组件中重复使用逻辑

但除了这些一般的优点外,一个用于获取数据的自定义钩子实现还提供了一个好处,即有一个单一的、可控的方式来处理请求状态,如当一个请求成功完成时(数据已被检索),请求处于加载状态(有一个基线加载状态的视觉提示 - Loader),或请求失败时(总是在控制台记录错误)。

这个好处对于提供直观的用户体验是非常重要的,它让用户知道每当有东西在加载时,总会有一个 "加载中... "的消息显示出来,或者有一个特定的Loader动画,而不是在应用程序的不同区域有不同的方式来处理这个状态(除非这是必须的,那么你总是可以向钩子传递某些参数,以便重载这个行为)。

实用的数据获取钩子实例

假设我们正在构建一个博客应用程序,类似于你正在阅读这篇文章的那个。我们将在应用程序的各个部分与文章进行大量的交互,许多文章部分有类似的设计和类似的行为。

如果你不使用钩子来获取数据,这些部分会看起来像这样:

function App() {
  const [requestState, setRequestState] = useState({
    data: [],
    loading: true,
    failed: false,
  });
  useEffect(() => {
    const retrieveArticles = async () => {
      try {
        const articles = await axios.get("our-endpoint");
        setRequestState((prevState) => ({
          ...prevState,
          data: articles,
        }));
      } catch (err) {
        setRequestState((prevState) => ({
          ...prevState,
          failed: true,
        }));
      } finally {
        setRequestState((prevState) => ({
          ...prevState,
          loading: false,
        }));
      }
    };
    retrieveArticles();
  }, []);
  return (
    <div className="App">
      {requestState.loading ? (
        <p>Data is currently loading...</p>
      ) : requestState.failed ? (
        <p>There was an issue loading the articles.</p>
      ) : (
        requestState.data.map((article) => (
          <Article title={article.title} body={article.body} />
        ))
      )}
    </div>
  );
}

正如你在上面的例子中所看到的,这是一个相当直接的在React中获取数据的方法的实现。然而,如果我们想做完全相同的请求,或者使用相同的配置,但使用不同的URL会发生什么?

嗯,你已经猜到了!我们必须重新做一遍,这并不是处理这种情况的最方便的方法,特别是如果应用程序在未来会大量增长。

那么,我们如何缓解这一过程呢?这就对了,钩子现在要发挥作用了。让我们看看下面这个带有钩子的再现的例子。

import axios from "axios";
import { useEffect, useState } from "react";
const useFetchData = (
  requestConfig, // Axios request config
) => {
  const localRequestConfig = requestConfig || {};
  const [state, setState] = useState({
    loading: true,
    data: null,
    error: null,
  });
  if (!localRequestConfig?.method) {
    localRequestConfig.method = 'GET';
  }
  useEffect(() => {
    if (localRequestConfig.url) {
      axios(localRequestConfig)
        .then((res) => {
          setState(prevState => ({
            ...prevState,
            data: res.data,
          }))
        })
        .catch((err) => {
          setState(prevState => ({
            ...prevState,
            error: err,
          }))
        })
        .finally(() => {
          setState(prevState => ({
            ...prevState,
            loading: false,
          }))
        });
    } else {
      setState(prevState => ({
        ...prevState,
        loading: false,
        error: new Error('No URL provided!'),
      }));
    }
    return state;
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [requestConfig]);
};
export default useFetchData;

现在我们已经创建了钩子,让我们也替换掉之前的实现,并将其添加到我们的应用程序中。

function App() {
  const {
    data: articles,
    loading: articlesLoading,
    error: articlesError,
  } = useFetchData();
  return (
    <div className="App">
      {articlesLoading ? (
        <p>Data is currently loading...</p>
      ) : articlesError ? (
        <p>There was an issue loading the articles.</p>
      ) : (
        articles.map((article) => (
          <Article title={article.title} body={article.body} />
        ))
      )}
    </div>
  );
}

所以,正如你所看到的,我们现在用一种更好的方式来重新利用API调用逻辑,而且没有遗漏任何功能。通过为钩子提供requestConfig参数,我们进一步提高了可重用性,因为用户可以简单地传递他需要的配置,仅此而已。没有更多的麻烦;它是简单的即插即用。

现在,如果我们需要在其他10个组件中使用API调用逻辑,我们就不需要在这里复制所有这块内容。

const [requestState, setRequestState] = useState({
    data: [],
    loading: true,
    failed: false,
  });
  useEffect(() => {
    const retrieveArticles = async () => {
      try {
        const articles = await axios.get("our-endpoint");
        setRequestState((prevState) => ({
          ...prevState,
          data: articles,
        }));
      } catch (err) {
        setRequestState((prevState) => ({
          ...prevState,
          failed: true,
        }));
      } finally {
        setRequestState((prevState) => ({
          ...prevState,
          loading: false,
        }));
      }
    };
    retrieveArticles();
  }, []);

这个片段就是我们需要复制的全部内容:

const {
    data: articles,
    loading: articlesLoading,
    error: articlesError,
  } = useFetchData();

Voila!现在我们已经实现了一个处理数据获取过程的钩子,我们只需复制5行代码,就可以在任何我们喜欢的地方重用它。当然,如果你确实需要扩展这个功能来处理其他情况,你可以简单地对钩子进行参数化,这样你就不会遇到任何不足之处。

只是为了让你有一个更好的视角,我们已经设法将组件文件的大小减少了这么多。

Comparison of how much code is removed after creation of react hook to handle data fetching

在使用钩子重构你的代码库之后,一个好的下一步是对你的请求进行缓存,以节省资源和提高性能。你可以在这里阅读我的另一篇文章,帮助你了解如何使用:https://upmostly.com/tutorials/how-to-integrate-cache-in-react-applications

最后的话

我希望你通过阅读这篇文章学到了一些新东西,并希望你喜欢这篇文章。下一篇再见

此外,如果你有任何建议、问题或提示要分享,请随时在下面留言。这将是非常感激的。

干杯!