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