React自定义钩子与普通函数有何区别?

104

我试图理解自定义钩子。我了解普通钩子,但我的问题是:编写自定义钩子时,它与普通函数有什么区别?我的意思是,为什么不称其为普通函数,而要将其称为 use*?

9个回答

53

我相信没有人准确回答了你的问题。我仍在了解拥有这个额外功能(称为hooks)的好处和目的,但我可以分享我的理解。

React Hooks 是具备 React 功能的 JS 函数。这意味着您可以将某些逻辑添加到普通 JS 函数中,并且还可以使用本地 hooks,如 useState、useEffect 等,增强该逻辑,以添加它的状态、副作用、记忆化等。

因此,钩子是管理组件逻辑的一种非常好的方式,使其隔离。

所以,您可以拥有一个 foo.component.js(UI)和一个 useFoo.js(逻辑),其中 useFoo 将包含许多 js 函数和一个 hook 来管理这些函数并返回预期结果。

以下是关于 React Hooks 的精彩视频,完全推荐:

https://youtu.be/J-g9ZJha8FE


谢谢分享,正是我在寻找的! - Alexander
24
基本上,如果您的可调用函数不需要 useState 或其他 React hooks,并且您允许它在 React 组件之外被调用,则使用一个简单的函数就可以了。 - ecoe
@ecoe,我的方法是首先创建一个简单的函数,如果不需要使用React,然后在需要的情况下添加一些React hooks - 将一个“简单”的函数移动/使用到hook中。 - undefined

36
  • 首先,如果您使用普通函数,在组件的每次重新渲染时,这个函数将会被再次创建,这会导致性能问题。您可能会认为可以通过使用useCallBack来解决问题,从而使React不会在每次重新渲染时创建一个新函数,但这并不是我们要解决的主要问题。
  • 正如React文档所讨论的那样,主要问题是避免在不同的有状态功能组件中复制和粘贴相同的逻辑代码。
  • 如果我们在组件内部使用普通函数并使用useCallBack来避免每次创建新函数,这并不能解决问题,因为在每个组件中,我们还需要复制此逻辑代码,所以这并没有解决问题。
  • 另一种解决方案是在函数组件之外创建一个函数来处理此逻辑,但存在一个大问题:在组件外部的普通函数中,我们不能访问状态,因为正如我们提到的那样,该实现逻辑是有状态的,并且我们只能在React组件中访问状态。
  • 那么什么是解决方案呢?没错,就是自定义React Hooks!

    1. 这是一个有状态函数,使用其他 React 内置钩子(例如 useStateuseCallback 等)来包装你想要集中在一个地方的有状态逻辑,避免在多个组件中复制和粘贴相同的逻辑。
    2. 使用这种方法,你可以将逻辑放在组件外部的另一个函数中,同时获得 React 有状态功能的好处。

    希望这个答案能解决您的问题并消除您的疑虑。


    在你的回答的第一部分(useCallback 部分),看起来你混淆了回调函数(在渲染阶段内创建)和普通函数与自定义钩子(都是在 React 生命周期之外创建)。然后最后的解决方案似乎并不符合你回答的前提!? - Emile Bergeron
    “问题”部分的第一点毫无意义。这是大多数人似乎误解的事情。首先,创建一个新函数几乎没有性能成本。其次,更重要的是,即使您将函数包装在useCallback中,它仍然会在每个渲染周期中被创建!它不会神奇地被忽略,它仍然存在于每个渲染周期的代码中。这是关于此函数的“身份”,无论如何使用useCallback都不会获得任何性能提升,因为它会重新创建该函数。 - Robo Robok
    重点是useCallback决定是否使用新创建的函数或忽略它,但无论如何都会在内存中创建,因为它作为参数传递。 useCallback的性能提升来自于使用这些函数作为prop的组件仅在此函数的标识更改时重新渲染。如果没有useCallback,则会在每次父级渲染时重新渲染,这通常是不必要的。 - Robo Robok

    23

    来自 React 文档

    自定义 Hook 是一个 JavaScript 函数,其名称以 "use" 开头,并且可以调用其他 Hooks。[...] 它的名称应始终以 "use" 开头,这样您就可以一眼看出Hooks 规则适用于它。

    那么为什么要使用特殊的 "use" 命名前缀来定义自定义 Hook?

    1.) 它告诉消费者,这些函数旨在与 React 一起使用并遵守隐含的契约(上述规则)。

    2.) 您可以获得工具支持,检查和执行这些规则。例如,eslint-plugin-react-hooks 利用启发式算法,假设以 "use" 前缀和大写字母开头的函数是 Hook。


    3
    这是正确的答案。自定义 hook 是一个函数,其使用受钩子规则的限制,就像内置 hook 一样。由于自定义 hook 通常包含钩子本身,因此我们必须确保只有在通常使用 hook 合法的地方才调用我们的自定义 hook。 - McMath

    21

    React Hooks(自定义或非自定义)应以use前缀开头。此外,根据React文档:

    1)Hooks只应从React代码中调用,而不是从常规JS函数中调用。因此,Hooks的范围仅限于React代码世界,并具有更多的功能来处理React代码。与JS不同,常规函数可以在整个应用程序中使用,但作为React代码指南,使代码更符合React语法。

    2)在基于类的组件中,Hooks无法工作,但常规函数可以。

    3)在常规JS函数中,您无法访问useStateuseEffectuseContext等,但在React自定义Hooks中可以访问。


    3
    不强制要求在名称后面加上"use"。只要函数的第一个字母大写,例如React函数中的useState、useEffect等,仍然可以使用。请参考以下示例:https://codesandbox.io/s/react-playground-crjbf?file=/index.js - GorvGoyl
    1
    是的,但ReactJS钩子的最佳实践建议它应该以'use'开头。 - Neha Sharma
    是的,但为什么?它提供了哪些好处?他们为什么首先将其添加到指南中? - Zahema
    1
    这个“答案”没有解释任何东西。 - Robo Robok
    从React文档中可以看到:“它的名称应始终以use开头,这样您就可以一眼看出Hooks规则适用于它。”此外,这也是ESLint用来区分钩子和普通函数的方法。 - Tamlyn

    7
    一个自定义 hook 依赖于一个或多个其他 hooks。React hooks 的设计初衷是在组件的渲染方法中使用。如果您尝试在其他地方使用 hook,则会收到警告。自定义 hooks 遵循与内置 hooks 相同的约定,因为它们必须以相同的方式使用。use 前缀只是一种约定,用于标识钩子函数,通常在组件渲染方法的最顶部调用。
    您可以随意命名您的 hook 函数,但是如我所提到的,如果在渲染方法之外使用,React 将发出警告。

    5
    也许我漏掉了什么,除了钩子的文档和规则之外,与普通函数相比有什么优势(除了代码检查功能)? - pandith padaya
    是的。你忽略了建议的事实,这样你就可以轻松地识别钩子和其他函数。话虽如此,你可以自由地按自己的意愿命名函数。 - Pablo
    1
    那么假设我将钩子命名为 myCustomFunction 而不是 useCustomFunction,除了代码检查和其他文档规则外,我不会失去任何功能,对吗? - pandith padaya
    不,你不会失去任何功能。这只是一个函数。但正如我所说,由于自定义钩子通常由其他钩子组成,如果您在上下文之外使用函数,您将收到错误,但这与函数名称本身无关。 - Pablo

    2

    你可以随意取名,它仍然能正常工作。唯一的优点是,如果你使用“useName”约定,React会检查错误,以确保它正确遵循钩子规则。这只是使你的任务稍微容易一些。


    0

    正如其他用户所述,自定义钩子或钩子通常用于我们需要执行与React组件相关的任何操作,而其他实用函数不与React状态绑定,无法在存在React状态逻辑的区域中工作。

    自定义钩子的一个示例是useCustomNavigation,它可以是一系列导航器函数,例如navigateToHome、navigateToCheckout等。因此,当您需要从代码的不同部分路由到主页时,只需使用此钩子。还可以将任何逻辑/功能(例如分析和副作用)作为navigateToHome函数的一部分。

    实用函数的一个示例可以是任何东西,例如capitalize,它与React或React组件无关。您不能创建名为navigator并添加useNavigation的实用函数。


    0

    在我看来,自定义钩子具有自己特定的目的和特点以及方法。

    我的意思是,在某些情况下,我们希望创建一个自定义钩子而不是一个函数。

    例如,如果您需要将一些数据存储在本地存储中,则需要为此创建一个名为“useLocalStorage”的自定义钩子;如果要创建一个组件,比如一个页面表单,您需要编写一个函数组件。

    区别在于,我们的钩子不是组件,不会在我们的UI上显示任何内容。它只是一个逻辑操作。

    我认为它们与上面的“逻辑”示例不同的原因是,我们的自定义钩子在允许使用其他自定义钩子相关的钩子方面是独特的。例如,“useDebugValue”只能在自定义钩子中使用。

    让我感到奇怪的是React区分函数和自定义钩子的方式,我认为这是混淆的主要原因。它“检查”您的函数名称,如果以“use”开头,则是自定义钩子。我认为更好的选择是将“const”声明更改为“hook”声明或给它一个独特的类型。

    简而言之,自定义钩子是逻辑,将允许使用像"useDebugValue"这样的钩子,而我们的函数大多与UI相关。


    0

    自定义钩子是调用其他 React 钩子的函数,因此必须在 React 组件的上下文中调用,并遵守钩子规则。命名约定会提醒你这一点。

    钩子中的代码运行方式与内联调用时完全相同,但通过将其放在钩子函数中,可使其可重用并具有清晰定义的输入和输出封装。


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