在React中编写你自己的useFetch钩子

200 阅读5分钟

一年多来,React钩子已经风靡一时。让我们来看看我们如何推出我们自己的useFetch 钩子来抽象出我们的组件的获取请求逻辑。

**注意:**这仅仅是为了学术目的。你可以推出你自己的useFetch 钩子并在生产中使用它,但我强烈建议使用像use-http这样的成熟库来为你完成繁重的工作。

我们的 useFetch 函数签名

为了确定我们的useFetch 函数签名,我们应该考虑我们可能需要从终端用户那里获得的信息,以实际执行我们的获取请求。在这种情况下,我们会说我们需要资源url ,我们需要可能与请求一起的options (例如,请求方法)。

function useFetch(initialUrl, initialOptions) {
  // Hook here
}

在一个功能更全面的解决方案中,我们可能会给用户一个终止请求的方法,但我们现在对我们的两个参数很满意

在我们的钩子中保持状态

我们的钩子将需要维护一些状态。我们至少需要维护urloptions 的状态(因为我们需要给用户提供一种方法来访问setUrlsetOptions )。还有一些其他的有状态的变量我们也需要

  • data (从我们的请求中返回的数据)
  • error (如果我们的请求失败,任何错误)
  • loading (一个布尔值,表示我们是否在积极获取)

让我们使用内置的useState 钩子来创建一堆有状态的变量。另外,我们要让我们的用户有机会做以下事情。

  • 设置网址
  • 设置选项
  • 查看获取的数据
  • 查看任何错误
  • 查看加载状态

因此,我们必须确保从我们的钩子中返回这两个状态设置函数和三个数据!

import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  // Some magic happens here

  return { data, error, loading, setUrl, setOptions };
}

重要的是,我们默认我们的urloptions 为钩子第一次被调用时提供的initialUrlinitialOptions 。另外,你可能会想,这些是很多不同的变量,你想把它们都维护在同一个对象里,或者几个对象里--这完全没问题!

当我们的URL或选项发生变化时运行一个效果

这是一个相当重要的部分!每当urloptions 变量发生变化时,我们都要执行一个fetch 请求。还有什么比内置的useEffect 钩子更好的方法呢?

import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // Fetch here
  }, [url, options]);

  return { data, error, loading, setUrl, setOptions };
}

用异步等待调用Fetch

比起Promise语法,我更喜欢async/await语法,所以让我们使用前者吧!当然,使用thencatchfinally ,而不是使用async/await,也一样可以工作。

import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    setError(undefined);

    async function fetchData() {
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setData(json);
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    }
    fetchData();
  }, [url, options]);

  return { data, error, loading, setUrl, setOptions };
}

这是一个很大的问题!让我们把它分解一下。当我们运行我们的效果时,我们知道我们开始获取数据了。因此,我们将我们的loading 变量设置为true ,并清除之前可能存在的任何错误。

在我们的异步函数中,我们用一个try/catch 块来包裹我们的fetch 请求代码。我们得到的任何错误都要报告给用户,所以在我们的catch 块中,我们setError ,以报告任何错误。

在我们的try 块中,我们做一个相当标准的fetch 请求。我们假设返回的数据是json ,因为我很懒,但如果我们想让这个钩子成为最通用的钩子,我们可能会给我们的用户一种方法来配置预期的响应类型。最后,假设一切顺利,我们将我们的data 变量设置为我们返回的JSON!

使用钩子

信不信由你,这就是创建我们的自定义钩子的全部内容!现在我们只需要把它引入到我们的网站中。现在我们只需要把它带入一个示例应用程序,并希望它能正常工作。

在下面的例子中,我有一个应用程序,可以加载任何github用户的基本github配置文件数据。这个应用几乎灵活运用了我们为钩子设计的所有功能,除了设置fetch 选项之外。我们可以看到,当获取请求正在加载时,我们可以显示一个 "加载 "指示器。当获取完成后,我们要么显示一个结果错误,要么显示一个字符串化的结果版本。

我们为用户提供了一种方法,可以输入不同的github用户名来执行新的fetch。一旦他们提交,我们使用从我们的useFetch 钩子导出的setUrl 函数,这将导致效果的运行和一个新的请求。我们很快就有了我们的新数据!

const makeUserUrl = (user) => `https://api.github.com/users/${user}`;

function App() {
  const { data, error, loading, setUrl } = useFetch(makeUserUrl('nas5w'));
  const [user, setUser] = useState('');

  return (
    <>
      <label htmlFor="user">Find user:</label>
      <br />
      <form
        onSubmit={(e) => {
          e.preventDefault();
          setUrl(makeUserUrl(user));
          setUser('');
        }}
      >
        <input
          id="user"
          value={user}
          onChange={(e) => {
            setUser(e.target.value);
          }}
        />
        <button>Find</button>
      </form>
      <p>{loading ? 'Loading...' : error?.message || JSON.stringify(data)}</p>
    </>
  );
}

欢迎在这里查看codesandbox上useFetch 钩子和示例应用程序。

结论性的想法

编写一个自定义的React钩子可以是一个有趣的尝试。起初,它有时会有点棘手,但一旦你掌握了它的窍门,就会非常有趣,而且可以真正缩短和减少组件代码中的冗余部分。