使用 React Hook 获取数据入门

使用 React Hook 获取数据入门

Flying
2019-11-06 / 0 评论 / 226 阅读 / 正在检测是否收录...

在本教程中,我想向您展示如何使用在 React Hook 中的状态和 Effect Hook 来获取数据。我们将使用众所周知的 Hacker News API 从科技界获取流行文章。您还将实现自定义 Hook 来获取数据,这些数据可以在应用程序的任何地方重用,也可以作为独立的节点包发布在 npm 上。

fetch-data-hook-hook.svg

如果您对 React 的新功能一无所知,请阅读 React Hook 的介绍。如果您想签出已完成的项目,以用于展示如何在 React Hook 中获取数据的示例,请签出这个 GitHub 存储库。

如果您只是想有一个随时可用的 React Hook 来获取数据:npm install use-data-api 并遵循文档。如果您使用它,不要忘记用星号 :-)

注意:React Hook 未来可能将不再用于在 React 中获取数据。相反,一个名为 Suspense 功能将负责获取。尽管如此,下面的演练是了解 React 中更多关于状态和 Effect Hook 的好方法。

使用 React Hook 获取数据

如果您不熟悉 React 中的数据获取,请查看我在 React 中的大量数据获取文章。它将带您了解使用 React 类组件的数据获取,如何使用 Render Prop 组件和高阶组件使其可重用,以及如何处理错误处理和加载器。

在本文中,我想通过函数组件中的 React Hook 向您展示所有这些功能。

import React, { useState } from 'react';

function App() {
  const [data, setData] = useState({ hits: [] });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

App 组件显示项目列表(hits = Hacker News articles)。状态和状态更新函数来自于名为 useState 的 State Hook,它负责管理我们将为 App 组件获取的数据的本地状态。初始状态是一个空匹配记录列表对象。目前还没有人为该数据设置任何状态。

我们将使用 axios 来获取数据,但是否使用另一个数据获取库或浏览器的本机获取 API 则取决于您。如果您还没有安装 axios,您可以在命令行使用 npm install axios 来安装。然后实现 Effect Hook 来获取数据:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

名为 useEffect 的 Effect Hook 用于使用 axios 从 API 获取数据,并使用状态 Hook 的更新函数将数据设置为组件的本地状态。Promise 解析发生在 async/await 中。

然而,在运行应用程序时,您应该会陷入一个糟糕的循环。Effect Hook 在组件挂载时运行,也在组件更新时运行。因为我们是在每次获取数据之后设置状态,组件会更新, Effect 会再次运行。它一次又一次地获取数据。这是一个 bug,需要避免。我们只想在组件挂载时获取数据。这就是为什么您可以提供一个空数组作为 Effect Hook 的第二个参数,以避免在组件更新时激活它,但仅用于组件的挂载。

useEffect(async () => {
  const result = await axios(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  setData(result.data);
}, []);

第二个参数可用于定义 Hook 所依赖的所有变量(在该数组中分配)。如果其中一个变量发生了变化,Hook 就会再次运行。如果包含变量的数组为空,则 Hook 在更新组件时根本不会运行,因为它不需要监视任何变量。

还有最后一个问题。在代码中,我们使用 async/await 从第三方 API 获取数据。根据文档,每个带有 async 注释的函数都返回一个隐式 Promise:async 函数声明定义了一个异步函数,它返回一个 AsyncFunction 对象。异步函数是通过事件循环异步操作的函数,使用隐式 Promise 返回其结果。。然而,Effect Hook 应该不返回任何东西或一个清理函数。这就是为什么您可能会在开发者控制台日志中看到以下警告:

警告:07:41:22.910 index.js:1452 Warning: useEffect function must return a cleanup function or nothing.
Promises and useEffect(async () => ...) are not supported,
but you can call an async function inside an effect..

这就是为什么不允许直接在 useEffect 函数中使用 async 。让我们通过在 useEffect 中使用 async 函数来实现一个解决方法。

useEffect(() => {
  const fetchData = async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);
    );

    setData(result.data);
  };

  fetchData();
}, []);

简单地说,这就是 React Hook 的数据获取。但是如果您对错误处理、加载指示符、如何触发从表单获取数据以及如何实现可重用的数据获取 Hook 感兴趣,请继续阅读。

如何以编程 / 手动方式触发 Hook?

很好,一旦组件挂载,我们就会获取数据。但是如果使用输入字段来告诉 API 我们对哪个主题感兴趣呢?Redux 被作为默认查询。但是关于 React 的话题呢?让我们实现一个 input 元素,使人们能够获取除 Redux 故事以外的其他故事。因此,为 input 元素引入一个新状态。

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

export default App;

目前,这两个状态彼此独立,但现在您希望将它们耦合起来,以便只获取 input 字段中查询指定的项目。在进行以下更改时,组件应该在挂载后按查询词获取所有项目。

const result = await axios(
    `http://hn.algolia.com/api/v1/search?query=${query}`,
);

缺少了一部分:当您试图在输入字段中键入内容时,在 Effect 触发挂载之后就没有其他数据获取了。这是因为您提供了空数组作为第二个参数。该 Effect 不依赖于任何变量,因此它只在组件挂载时被触发。但是,现在 Effect 应该取决于查询。一旦查询更改,应该再次触发数据请求。

useEffect(() => {
  const fetchData = async () => {
    const result = await axios(
      `http://hn.algolia.com/api/v1/search?query=${query}`,
    );

    setData(result.data);
  };

  fetchData();
}, [query]);

一旦更改了 input 字段中的值,数据的重新获取就会工作。但这又带来了另一个问题 : 在 input 字段中输入每个字符时,Effect 会被触发,并执行另一个数据获取请求。如何提供一个按钮来触发请求,从而手动触发 Hook?

function App() {
    ...
    const [search, setSearch] = useState('');
    ...
    return (
      <Fragment>
        ...
        <button type="button" onClick={() => setSearch(query)}>
          Search
        </button>
        ...
      </Fragment>
    );
  }

现在,让 Effect 依赖于搜索状态,而不是随 input 字段中的每次击键而变化的波动查询状态。一旦用户点击按钮,新的搜索状态就设置好了,应该会手动触发 Effect Hook。

...

function App() {
 ...
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [search]);

  return (
    ...
  );
}

export default App;

此外,搜索状态的初始状态被设置为与查询状态相同的状态,因为组件也在挂载时获取数据,因此结果应该反映输入字段中的值。然而,拥有类似的查询和搜索状态有点令人困惑。为什么不将实际 URL 设置为状态而不是搜索状态呢 ?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

带有 React Hook 的加载指示器

让我们为数据获取引入一个加载指示器。它只是由状态 Hook 管理的另一个状态。加载标志用于在 App 组件中呈现加载指示器。

function App() {
  ...
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      ...
      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      ...

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        ...
      )}
    </Fragment>
  );
}

export default App;

一旦该 Effect 被用于数据获取(当组件挂载或 URL 状态更改时发生),加载状态将被设置为 true 。一旦请求解决,加载状态再次设置为 false

使用 React Hook 进行错误处理

用 React Hook 获取数据的错误处理呢 ? 这个错误只是用状态 Hook 初始化的另一个状态。一旦出现错误状态,App 组件就可以为用户呈现反馈。当使用 async/await 时,通常使用 try/catch 块进行错误处理。您可以在 Effect 中这样做:

function App() {
  ...
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      ...

      {isError &amp;&amp; <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

export default App;

使用表单和响应获取数据

怎样获取数据的合适形式呢?到目前为止,我们只有 input 字段和按钮的组合。在引入更多 input 元素之后,可能需要用 Form 元素包装它们。此外,表单也可以触发键盘上的 Enter 按钮。

function App() {
  ...

  return (
    <Fragment>
      <form
        onSubmit={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError &amp;&amp; <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

但是现在,当单击提交按钮时,浏览器会重新加载,因为这是浏览器在提交表单时的原生行为。为了防止默认行为,我们可以在 React 事件上调用一个函数。在 React 类组件中也是这样做的。

function App() {
  ...

  return (
    <Fragment>
      <form onSubmit={event => {
        setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);

        event.preventDefault();
      }}>
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError &amp;&amp; <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

现在,当您单击提交按钮时,浏览器应该不再重新加载了。它像以前一样工作,但这一次使用的是表单,而不是简单的 input 字段和按钮组合。您也可以按键盘上的 Enter 键。

3

评论 (0)

取消