此文出现在2018年,虽已时隔3年,仍在React官网中挂着,特此留存,其中删减了一些非必要内容。
为什么使用Hooks?
我们知道组件和自顶向下的数据流可以帮助我们将大型UI组织成小的、独立的、可重用的部分。但是,我们通常不能进一步分解复杂组件,因为逻辑是有状态的,不能提取到函数或其他组件中。这就是有时人们说 React 不让他们“关注分离”的意思。
这些情况非常常见,包括动画、表单处理、连接外部数据源,以及我们想要从组件完成的许多其他事情。当我们试图单独用组件解决这些问题时,我们通常会出现以下问题:
- 难以重构和测试的庞大组件。
- 不同组件和生命周期方法之间的重复逻辑。
- 复杂的模式,如渲染道具和高阶组件。
我们认为 Hooks 是我们解决所有这些问题的最佳方式。Hook 让我们把组件内部的逻辑组织成可重用的独立单元:
Hooks在组件内部应用React原理(显式数据流和组合),而不仅仅是在组件之间。这就是为什么我觉得Hooks很适合React组件模型。
与render props或高阶组件等模式不同,Hooks不会在组件树中引入不必要的嵌套。它们也没有mixins的缺点。
即使你本能的拒绝(就像我一开始做的那样!),但我仍然鼓励你抱着尝试的态度去使用它。我想你会喜欢的。
Hooks会使React变得臃肿吗?
在我们详细讨论 Hooks 之前,您可能会担心我们只是在 React 中额外添加了 Hooks 的概念。这是一个公正的评论。我认为,虽然学习它们肯定会有短期的认知成本,但最终结果将是值得的。
使用 Hooks 编写 React 应用程序时,你需要处理的概念数量就会减少。Hooks 让你可以一直使用函数,而无需在函数、类、高阶组件和 render props 之间不断切换。
在包大小方面,对Hook 的支持只增加了 React 大约1.5kB (min+gzip)。虽然这不算多,但采用 Hooks 也可能会减少您的包大小,因为使用 Hooks 的代码往往比使用类的等价代码更容易缩小。下面这个例子有点极端,但它有效地说明了为什么(点击查看整个线程):
Hooks 提议不包括任何破坏性的更改。即使在新编写的组件中采用了Hooks,现有代码也会继续工作。事实上,这正是我们的建议——不要做任何大的重写!在任何关键代码中采用Hooks都是一个好主意。
Hooks到底是什么?
要理解 Hooks,我们需要退后一步,考虑代码重用。
如今,有很多方法可以在 React 应用中进行逻辑重用。我们可以编写简单的函数并调用它们来计算一些东西。我们还可以编写组件(组件本身可以是函数或类)。组件更强大,但它们必须呈现一些UI。这使得它们不方便共享非可视化逻辑。这就是我们如何结束如 render props 和高阶组件等复杂的模式。如果只有一种重用代码的通用方法,React 不是会更简单吗?
函数似乎是代码重用的完美机制。在函数之间因逻辑变更而花费的精力最少。然而,函数内部没有本地 React 的 State。在不重新构造代码或引入像 Observable 这样的抽象类的情况下,便无法在类组件中提取“观察窗口大小并更新状态”或“随时间变化使一个值动起来”之类的行为。这两种方法都损害了React的简单性。
Hooks 恰好解决了这个问题。Hooks 允许您在一个函数中通过执行一个函数调用,使用 React 特性(如State)。React 提供了一些内置的 Hooks 来暴露 React 的“构建块”:state、生命周期和上下文。
因为 Hooks 是常规的 JavaScript函数,你可以把 React 提供的内置 Hooks 组合成你自己的“自定义Hooks”。这让你把复杂的问题转化为一行程序,并在你的应用程序或React社区中共享它们。
注意,自定义Hooks在技术上并不是React特性。它的编写需要遵循于Hooks的设计方式。
给我一些代码!
假设我们想要在一个组件中订阅当前的窗口宽度(例如,在一个狭窄的视口上显示不同的内容)。
现在有几种方法可以编写这种代码。如编写一个类,设置一些生命周期方法,或者甚至可以提取 render props 或更高阶组件(如果您想在组件之间重用它)。但我认为没有什么比下面的代码更好的了:
https://gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae
如果你读了这段代码,你会发现它完全按照它所表现的去执行。我们在组件中使用窗口宽度,如果组件发生变化,React 会重新渲染组件。这就是 Hooks 的目标——使组件具有真正的声明性,即使它们包含状态和副作用。
让我们看看如何实现这个自定义 Hooks 。我们将使用 React 本地状态来保持当前窗口的宽度,并在窗口调整大小时使用一个副作用来设置该状态:
https://gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae
正如你在上面所看到的,内置的 React Hooks 如 useState 和 useEffect 可以作为基本的构建块。我们可以直接在组件中使用它们,或者我们可以将它们组合成自定义钩子,比如useWindowWidth。使用 自定义Hooks 的感觉就像使用 React 的内置API一样。
您可以从这个概述中了解更多关于 内置Hooks 的信息。
Hooks 是完全封装的——每次你调用一个 Hook,它在当前执行的组件中拥有被隔离的本地状态。对于这个特定的例子来说,这并不重要(窗口宽度对于所有组件都是一样的!),但正是它让 Hooks如此强大。它不是一种共享状态的方式——而是一种共享有状态逻辑的方式。我们不想破坏自顶向下的数据流!
每个 Hook 可能包含一些局部状态和副作用。可以在多个 Hooks 之间传递数据,就像通常在函数之间传递数据一样。它们可以接受参数和返回值,因为它们是JavaScript函数。
下面是一个 React 动画库使用 hook 的例子:
https://codesandbox.io/embed/ppxnl191zx?fontsize=14&hidenavigation=1&theme=dark
注意在演示源代码中,这个惊人的动画是如何通过在同一个呈现函数中通过几个自定义钩子传递值来实现的。
https://codesandbox.io/s/ppxnl191zx
在 Hooks 之间传递数据的能力使它们非常适合表示动画、数据订阅、表单管理和其他有状态的抽象。与 render props 或高阶组件不同,Hooks 不会在渲染树中创建一个“错误的层次结构”。它们更像是附加到组件上的“存储单元”列表。没有额外的层。
那么 Class 呢?
在我们看来,自定义Hooks 是 Hooks 提议中最吸引人的部分。但是为了让自定义Hooks工作,React 需要为函数提供一种声明状态和副作用的方法。这正是 useState 和 useEffect 等内置Hooks处理的内容。您可以在文档中了解它们。
事实证明,这些内置钩子不仅用于创建 自定义Hooks。一般来说,它们也足以定义组件,因为它们为我们提供了所有必要的特性,比如状态。这就是为什么我们希望 Hooks 成为未来定义 React 组件的主要方式。
我们没有弃用类的计划。在Facebook,我们有成千上万的类组件,和你一样,我们不打算重写它们。但是如果 React 社区支持 Hooks,那么推荐两种不同的方式来编写组件就没有意义了。Hooks 可以覆盖类的所有用例,同时在提取、测试和重用代码方面提供更多的灵活性。这就是 Hooks 代表 React 未来愿景的原因。
但 Hooks 是否具有魔力?
你可能会对 Hooks规则 感到惊讶。
虽然必须在顶层调用 Hooks 的情况很少见,但即使可以,您可能也不想在条件中定义状态。例如,您也不能在类中有条件地定义状态,在与React用户交谈的四年中,我从未听到过关于这一点的抱怨。
这种设计对于启用 自定义Hooks 而不引入额外的语法或其他缺陷至关重要。我们认为一起提供的功能相比,这种权衡是值得的。如果你不同意,我鼓励你在实践中使用它,看看这会不会改变你的感觉。
我们已经在产品中使用 Hooks 一个月了,看看工程师们是否对这些规则感到困惑。我们发现,在实践中,人们在几个小时内就能适应它们。就我个人而言,我承认一开始我也觉得这些规则“不对”,但我很快就克服了。这段经历反映了我对React的第一印象。(你喜欢立即反应吗?直到我第二次尝试才知道。)
请注意,在 Hooks 的实现中也没有“魔法”。正如杰米所指出的,它看起来很像这个:
https://gist.github.com/gaearon/62866046e396f4de9b4827eae861ff19
我们为每个组件保存一个 Hooks 列表,并当 Hook 执行完成后移动到列表中的下一项。多亏了 Hooks 规则,它们在每次渲染时的顺序是相同的,所以我们可以为每个调用提供正确的状态。不要忘记,React 不需要做任何特殊的事情来知道哪个组件正在渲染——React 就是调用你的组件。
( Rudi Yardley的这篇文章包含了一个很好的视觉解释!)
也许您想知道 React 将 Hooks 的状态保存在哪里。答案是它被保存在与 React 保存类状态相同的地方。React 有一个内部更新队列,无论如何定义组件,它是任意状态的真实来源。
Hooks 不依赖代理或 getter,这在现代 JavaScript 库中很常见。所以可以说,对于类似的问题,Hooks 没有一些流行的方法那么神奇。我会说 Hooks 和调用数组一样神奇。array.push 和 array.pop 。(对于它,调用顺序也很重要!)
Hooks 的设计与 React 无关。事实上,在提案发布后的几天里,不同的人提出了针对Vue、Web Components、甚至JavaScript函数的Hooks API的实验性实现。
最后,如果您是一个纯粹的函数式编程主义者,并且对 React 依赖可变状态作为实现细节感到不安,那么您可能会对使用代数的影响(algebraic effects)(如果JavaScript支持它们)以纯函数方式实现 Hooks 感到满意。当然,React 在内部总是依赖于可变状态——确切地说,你不需要这样做。
无论你是从更务实的角度还是从教条的角度考虑(如果你有的话),我希望这些理由中至少有一个是合理的。如果你很好奇,Sebastian (Hooks提案的作者)也在RFC的评论中回应了这些和其他关注。最重要的是,我认为 Hooks 让我们以更少的努力构建组件,并创建更好的用户体验。这就是为什么我个人对 Hooks 很感兴趣。
—— 后面两段未已删减 ——