发布于 2026-01-05 6 阅读
0

如何在 React 中创建自定义 Hooks

如何在 React 中创建自定义 Hooks

什么是自定义挂钩?

自定义钩子允许你将一些组件逻辑提取到一个可重用的函数中。

自定义钩子是一个 JavaScript 函数,它以`use`开头,并且可以调用其他钩子。请记住,组件和钩子都是函数,所以我们实际上并没有创造任何新概念。我们只是将代码重构为另一个函数,使其可重用。

如果没有自定义钩子

假设我们的组件中有一个用于获取窗口宽度的功能。我们想知道用户何时调整屏幕大小。例如,为了修改设计,我们需要知道屏幕何时变小。我们可以这样写:

const LayoutComponent = () => {
  const [onSmallScreen, setOnSmallScreen] = useState(false);

  useEffect(() => {
    checkScreenSize();
    window.addEventListener("resize", checkScreenSize);
  }, []);

  let checkScreenSize = () => {
    setOnSmallScreen(window.innerWidth < 768);
  };

  return (
    <div className={`${onSmallScreen ? "small" : "large"}`}>
      <h1>Hello World!</h1>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

这里有一个组件,它有一个onSmallScreen状态,用于判断当前窗口的宽度是否小于 768 像素。为了实现这一点,我们使用了一个useEffect hook。在这个 hook 内部,我们首先调用checkScreenSize函数来更新onSmallScreen状态变量。最后,我们将checkScreenSize函数绑定到一个 resize 事件监听器,以便在 resize 事件发生时,根据需要更新该状态。

创建自定义钩子

这样做完全没问题。当窗口宽度小于 600 像素时,类名就变成small;当宽度大于 600 像素时,类名就变回large

现在,假设我想在应用程序的其他地方使用控制窗口宽度的功能。我应该直接复制粘贴这段代码吗?当然可以,但我们可以将此功能提取到一个自定义钩子中,并在任何需要的地方重复使用。

因为 hooks 只是 Javascript 函数,所以它们实际上不需要 React 组件就能存在。

我将创建一个名为useWindowWidth.js的新文件:

import { useState, useEffect } from "react";

const useWindowsWidth = () => {
  const [isScreenSmall, setIsScreenSmall] = useState(false);

  let checkScreenSize = () => {
    setIsScreenSmall(window.innerWidth < 600);
  };
  useEffect(() => {
    checkScreenSize();
    window.addEventListener("resize", checkScreenSize);

    return () => window.removeEventListener("resize", checkScreenSize);
  }, []);

  return isScreenSmall;
};

export default useWindowsWidth;
Enter fullscreen mode Exit fullscreen mode

我们将此功能提取到了useWindowWidth函数中。现在,我们可以在任何需要使用它的地方导入它!

import React from 'react'
import useWindowWidth from './useWindowWidth.js'

const MyComponent = () => {
  const onSmallScreen = useWindowWidth();

  return (
    // Return some elements
  )
}

Enter fullscreen mode Exit fullscreen mode

是不是很棒?我之前的一个项目里也用到过类似的功能。我需要知道窗口的大小,以便调整渲染的元素。使用自定义钩子减少了重复代码的数量。

当然,任何在组件的 hook 中使用到的内容都可以提取出来,并在自定义 hook 中使用。

例如,假设你有一些组件,用于显示基于文章的评论列表。我们可以设想类似这样的情况:


const ArticleWithComments = (articleId) => {
  const [comments, setComments] = useState([])
  const [error, setError] = useState(null)

  let handleCommentsSuccessFetch = (articleComments) => setComments(articleComments)

  let handleError = error => setError(error)

  useEffect(() => {
    fetchComments(articleId, handleCommentsSuccessFetch, handleError)
  }, [])

  return (
    // Do something in the DOM
  )
}

const BlogPostWithComments = (blogPostId) => {
  const [comments, setComments] = useState([])
  const [error, setError] = useState(null)

  let handleCommentsSuccessFetch = (blogPostComments) => setComments(blogPostComments)

  let handleError = error => setError(error)

  useEffect(() => {
    fetchComments(blogPostId, handleCommentsSuccessFetch, handleError)
  }, [])

  return (
    // Do something in the DOM
  )
}

Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们有两个组件。它们都根据 ID 获取评论列表,ID 可以是文章 ID,也可以是博客文章 ID。在useEffect hook 中,我们调用了一个 API 来获取这些评论,并执行两个函数。第一个函数在成功时将评论设置到状态中,第二个函数在出错时将错误信息设置到状态中。

但是,这两个组件的功能存在重复。幸运的是,我们可以将此功能提取到自定义钩子中:

const useCommentsRetriever = (entityId) => {
  const [comments, setComments] = useState([]);
  const [error, setError] = useState(null);

  let handleCommentsSuccessFetch = (comments) => setComments(comments);

  let handleError = (error) => setError(error);

  useEffect(() => {
    fetchComments(entityId, handleCommentsSuccessFetch, handleError);
  }, []);

  return [comments, error];
};
Enter fullscreen mode Exit fullscreen mode

这里,我们引入了`useCommentsRetriever`这个钩子函数。它接受一个`entityId`作为参数,也就是我们文章或博客文章的 ID。它和组件中的钩子函数类似,区别在于这个自定义钩子函数需要返回一些内容。我选择返回一个数组,数组的第一个元素是评论,第二个元素是错误信息。

它的用法如下:

//Import the custom hook
import useCommentsRetriever from './useCommentsRetriever.js'

const ArticleWithComments = (articleId) => {

  const [comments, error] = useCommentsRetriever(articleId)

  return (
    // Do something in the DOM
  )
}

const BlogPostWithComments = (blogPostId) => {

  const [comments, error] = useCommentsRetriever(blogPostId)

  return (
    // Do something in the DOM
  )
}
Enter fullscreen mode Exit fullscreen mode

看看我们少写了多少代码?`useCommentsRetriever`函数接受一个 id 作为参数。这[comments, error]就是我们所说的数组解构。`useCommentsRetriever`函数返回一个数组。我们将该数组的第一个元素赋值给名为 `comments` 的变量,将第二个元素赋值给名为 `error` 的变量。

请注意,我可以随意命名这些变量。我也可以在两个组件中使用不同的名称。因此,当您在useState hook 中看到相同的语法时,这是因为useState hook 也返回一个数组 😉

我有一篇关于状态钩子的文章,如果你想了解更多,可以看看。

我们必须以“使用”开头来创建自定义钩子吗?

根据 React 文档,是的。

React 文档中关于自定义 Hook 的部分从使用开始。

您可以查看文档中的钩子规则以获取更多信息。

隔离自定义钩子

如果在两个组件中使用相同的自定义 Hook,它们将不会共享状态。例如,BlogPostWithComments 组件的状态将与 ArticleWithComments 组件的状态完全分离。每个自定义 Hook 都会创建一个新函数,该函数会使用React 的useStateuseEffect。我们可以在同一个组件中使用多个 Hook,逻辑相同。

天空才是极限

自定义 Hooks 让你在编写 React 代码时真正发挥想象力。你可以提取并共享逻辑,这是类组件无法实现的。

玩得开心❤️

文章来源:https://dev.to/damcosset/how-to-create-custom-hooks-in-react-44nd