2018 ReactConf 大会上,React 官方宣布 React v16.7.0-alpha 将引入 Hooks,乍一看,你可能在想 Hooks 是什么?有什么用?且看下文分析。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
Hooks 是什么?
Hooks 是一种函数,该函数允许你“勾住(hook into)”React 状态和来自函数组件的生命周期功能。Hook 在类内部不起作用,它们允许你无需类就使用 React。(不建议你马上开始重写你现有的组件,但你可以在新组件中开始使用 Hook。)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
React 提供了一些内置 Hook,如 useState,你也可以创建自定义 Hooks 以在不同的组件中复用有状态行为。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
根据 React 官方给出的文档,Hooks 主要分为以下几种:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
- State Hooks
- Effect Hooks
- 自定义 Hooks
该示例显示了一个计数器,点击该按钮,值会递增。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在这里,useState 是一个 Hook,我们在函数组件中调用它以给它添加一些本地状态。React 将在重新渲染之间保留这个状态。useState 返回一对值:currentstate 值和允许你更新它的函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
可以从事件处理程序或其他位置调用该函数,这与类中的 this.setState 类似,除了其不会把旧的和新的状态合并在一起。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
声明多个状态变量文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
可以在单个组件中多次使用 State Hook:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
数组解构语法允许我们给状态变量取不同的名字,这些变量是我们通过调用 useState 声明的,这不是 useState API 的一部分。相反,React 假设如果多次调用 useState,那么在每次渲染的时候要遵循相同的顺序来做。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
Effect Hook
你之前很可能已经执行了数据提取、订阅、或手工改变来自 React 组件的 DOM。我们称这些操作为“副作用(side effect)”,因为它们会影响其他组件,并且在渲染过程中无法完成。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
Effect Hook、useEffect,增加了从函数组件执行副作用的功能。它与 React 类中的 componentDidMount、componentDidUpdate、和 componentWillUnmount 有相同的功能,但是统一为单个 API。(我们将展示一个例子,该示例在 Using the Effect Hook 中对 useEffect 和这些方法进行比较。)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
例如,此组件在 React 更新 DOM 后设置文档标题:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
当你调用 useEffect 时,你通知 React 在刷新对 DOM 的更改后运行你的“effect”函数。Effect 在组件内声明,因此可以访问其 props 和 state。默认情况下,React 在每次渲染后运行 effect,包括第一次渲染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
效果还可以通过返回一个函数来指定它们之后如何“清理”。例如,此组件使用 effect 来订阅朋友的在线状态,并通过取消订阅来清理:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
import { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
在此示例中,当组件卸载时,以及在由于后续渲染而重新运行 effect 之前,React 将取消订阅我们的 ChatAPI。(如果我们传递给 ChatAPI 的 props.friend.id 没有变化,有办法让 React 跳过重新订阅。)就像使用 useState 一样,可以在组件中使用多个 effect:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
Hook 允许你通过部件的关联情况(例如添加和删除订阅)来组织组件中的副作用,而不是基于生命周期方法强制拆分。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
自定义 Hooks
自定义 Hooks 主要用来复用组件逻辑,详情请阅读:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
https://reactjs.org/docs/hooks-overview.html文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
Hook 是 JavaScript 函数,但强加了两个额外的规则:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
- 只能在顶层调用 Hook,不要在循环、条件或嵌套函数中调用 Hook。
- 仅从 React 功能组件调用 Hook。不要从常规 JavaScript 函数调用 Hook。(还有另一个有效的地方来调用 Hook,即你的自定义 Hook。)
这里(https://www.npmjs.com/package/eslint-plugin-react-hooks)提供了一个 linter 插件来自动执行这些规则,这些规则乍看起来有些令人疑惑,但它们对 Hook 的良好运行至关重要。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
至于为何要在 React 中引入 Hooks,React 官方给出了答案:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
Hooks 解决了 React 中各种看似不相关的问题,这些问题是我们在开发和维护数以万计的组件时遇到的。无论你是在学习 React,还是每天在使用它,还是选择使用具有类似组件模型的其他库,你都可能会发现这类问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
React 没有提供可将可重用行为“附加”到组件的方法。如果你用过 React 一段时间,可能会对渲染 prop 和高阶组件等模式比较熟悉,它们试图解决这个问题。但是这些模式要求你在使用它们时重构组件,这可能很麻烦,并且代码会变得难以维护。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
如果你看一下 React DevTools 中的典型 React 应用程序,你可能会发现“包装器地狱”,组件被层层的提供者、消费者、高阶组件、渲染 prop 和其他抽象组件组包围。虽然我们可以在 DevTools 中过滤掉它们,但这反应了一个更深层次的问题:React 需要一个更好的原语来共享有状态逻辑。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
使用 Hooks,你可以从组件中提取有状态逻辑,可以进行独立测试和重用。Hooks 允许你在不更改组件层次结构的情况下重用有状态逻辑。这样可以轻松地在多个组件之间或与社区共享 Hooks。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
我们通常维护的组件都先从简单的开始,然后逐渐加入无法管理的状态逻辑和副作用。每个生命周期方法通常包含不相关逻辑的混合。例如,组件可能会在 componentDidMount 和 componentDidUpdate 时获取数据。不过,相同的 componentDidMount 方法可能还包含一些设置事件监听器的无关逻辑,并在 componentWillUnmount 中执行清理工作。一起更改的相互关联的代码会被拆分,但完全不相关的代码最终会组合在一个方法中。这样就很容易引入错误和导致不一致。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
在很多情况下,很难将这些组件分解为更小的组件,因为状态逻辑到处都是。测试它们也很困难。这是很多人更喜欢将 React 与单独的状态管理库相结合的原因之一。不过,这通常会引入太多的抽象,要求你在不同的文件之间跳转,让重用组件变得更加困难。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
为了解决这个问题,Hooks 让你可以根据相关的部分将一个组件拆分为较小的函数,而不是基于生命周期方法进行强制拆分。你还可以选择使用 reducer 来管理组件的本地状态,以使其更具可预测性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
根据我们的观察,类是学习 React 最大的障碍。你必须了解 JavaScript 中的 this 是怎么回事,这与它在大多数语言中的使用方法有很大不同。你必须记住绑定事件处理程序。人们可以很好地理解 prop、状态和自上而下的数据流,但对类的使用仍然感到很挣扎。React 中的函数和类组件之间的区别以及何时使用哪个组件在经验丰富的 React 开发人员之间也存在分歧。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
另外,React 已经推出了大约五年时间,Facebook 希望 React 在未来五年充满活力。正如 Svelte、Angular、Glimmer 和其他项目所表明的那样,预编译组件具有很大的潜力。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
最近,他们一直在尝试使用 Prepack(https://prepack.io/)进行组件折叠,我们已经看到了充满希望的结果。不过,他们发现类组件的无意识模式可能会使这些优化回退。在类上应用现今的一些工具时也存在一些问题。例如,类难以进行 minify,并且类会导致热重载变得不可靠。他们希望提供一种 API,使代码更有可能保持在可优化的路径上。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
为了解决这些问题,Hooks 让你可以在不使用类的情况下使用更多的 React 特性。从概念上讲,React 组件很接近于函数。Hooks 拥抱函数,但不会以牺牲 React 精神为代价。Hooks 提供了命令式的编程方式,不需要你学习复杂的函数式或反应式编程技术。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
例子:https://reactjs.org/docs/hooks-overview.html文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
我们知道,React 开发人员专注于产品,他们没有时间研究发布的每个新的 API。Hooks 是新东西,在学习或采用它们之前先等待更多的示例和教程,这样可能会更好。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
我们也知道为 React 添加新原语的标准是非常高的。对于好奇的读者,这里准备了一个详细的 RFC(https://github.com/reactjs/rfcs/pull/68),其中包含更多的细节,并为特定设计决策和现有相关技术提供了额外的视角。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
关键的是,Hooks 可以与现有代码同时存在,因此你可以逐步采用它们。React 官方正在分享这个实验性的 API,以便从社区中那些有兴趣打造 React 未来的人那里获得早期反馈,将公开进行 Hooks 的迭代。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
最后,没有必要急于使用 Hooks。建议避免任何“重大的重写”,特别是对于复杂的现有类组件来说。进入“Hooks 思考”模式需要精神上的转变。根据经验,最好先在非关键的新组件中练习使用 Hooks,并确保团队中的每个人都能适应。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
React 官方打算让 Hooks 涵盖所有与类相关的用例,但在可预见的未来,他们将继续支持类组件。在 Facebook 有数万个类组件,他们表示绝对没有计划重写它们。相反,他们开始在新代码中使用 Hooks。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
怎样使用 Hooks?
假设我们正在尝试编写一个 Counter 组件,正常的实现如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
而基于 Hooks 的实现将如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
使用 Hooks 的好处非常明显:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7578.html
- 减少代码实现;
- 共享逻辑,可以将逻辑解耦。