Hooks是React 16.8的新特性。这些特性让你不必专门写class组件,来使用state和其他React特性。
_
Hooks向后兼容。这一讲是为有经验的React用户提供的Hooks概览。是一个快熟浏览。如果你迷惑不解,就看这样的黄框提示:
细节解释 阅读动机来学习为什么我们介绍React Hooks
每一小节结束都会有这个,它们链接到相关细节。
State Hook
下例渲染了一个计数器,当你点击button,就会增加计数:
import React, { 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返回一对值:当前的状态值以及一个让你更新当前状态值的函数。你能在事件处理函数或者其他地方随便使用这个函数。就像this.setState在class中一样,只是它不会将新旧状态合并在一起(我们在使用State Hook这一讲中会对比useState和this.state的区别)。
useState的唯一函数是设置初始值。在上面的例子中,0就是计数器开始的值。注意这不像this.state,这里的state不是一个对象object——如果你想是object也是可以的。初始状态就在第一次渲染时使用一次。
声明多个状态变量
你可以在统一组件中使用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的一部分。反之,如果你调用多次useState,React会假想在每一次渲染的时候它们都有相同的顺序。我们稍后会回来来研究为什么是这个工作机制,什么时候有用。
但什么是Hook?
Hooks是让你把React的state和生命周期特性“钩进来”(到函数组件里面)的函数。Hooks在class中不能工作——它们本来就让你脱离class的。(我们不推荐重写现有组件,但你可以在新的组件中开始使用它们)
React提供了一些内建Hooks,像useState。你也能新建你自己的Hooks来在组件之间复用有状态的行为。我们先看内建组件。
细节解释: 你可以学习更多关于State Hook在“使用State Hook”这一讲
Effect Hook
之前的React组件中,你可能已经执行过数据请求,订阅,或者手动修改DOM。我们把这些行为叫做“side effect(副作用)”或者简称effect, 因为它们可能影响其他组件,同时可能在渲染期间无法完成。
副作用的钩子,useEffect,为函数组件增加了处理副作用的能力。它和React class中的componentDidMounr,componentWillUnmount,componentDidUpdate的目的相同,但是却在单一的API中达到统一。(在“使用Effect Hook”这一讲中,我们将会展示useEffect和这些方法对比的例子)
举例来说,该组件在React更新DOM后设置document title:
import React, { 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的变化后调用你的副作用函数。effects副作用是在组件内部声明的,所以它能访问props以及state。默认的是,React每次渲染都会运行这些effects副作用——包括第一次渲染。(我们将在后面课程中对比生命周期方法)
Effects副作用也也可以通过返回一个函数来明确之后怎样被清除。比如说,这个组件使用了effect副作用来订阅朋友的在线状态,以及通过取消订阅来清除:
import React, { 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';
}
在这个例子中,React将通过ChatAPI取消订阅,当组件卸载的时候,但同时也在卸载前,在一系列随后渲染期间重新运行这个effect副作用。(在“使用Effect Hook”中有教你让React在this.props.firend没有改变的时候,不要重新订阅的方法)
正如useState,你可以在组件中写不止一个副作用:
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);
}
// ...
Hooks使你在组件中通过代码片段的相关性来组织副作用代码(就像添加或者移除订阅一样),而不是把他们强行拆散到生命周期方法中。
细节解释: 你可以学习更多关于State Hook在“使用State Hook”这一讲
Hooks的规则
Hooks是JavaScript函数,但是它需要遵循两个额外的规则:
- 只能在顶级调用Hooks。不要调用Hooks在循环,条件和嵌套的函数中;
- 只能在React的函数组件中使用。
也不要在普通的Javascript函数中调用。(这其实是另外一种有用的地方——你的自定义Hooks,我们随后会介绍它们)
我们提供了一个linter plugin插件来自动确保这些规则。我们理解这些规则可能最开始看上去充满限制或者迷惑,但却对于让Hook顺利工作是十分重要的。
细节解释: 你能学到更多内容在后续章节“Hooks的规则”
构建你自己的Hook
有时候,我们想在组件间重用一些有状态的逻辑。传统情况下有两种流行解法:高阶组件和渲染props。自定义Hooks能让我们不需要在组件树中增加更多的组件而达成我们的需求。
上文中我们介绍了FriendStatus组件,通过使用useState和useEffect来订阅朋友的在线状态。那么我们也想在其他组件里面使用这个订阅的逻辑。
首先,我们把这个逻辑提取成一个自定义Hook,就叫useFriendStatus:
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
它把friendID作为参数,返回朋友在线的状态。
现在我们在多个组件中使用它:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
这些组件的状态是完全独立的。Hooks是一种复用有状态逻辑的方法,但不是状态本身。实际上,每一次调用Hook就一个独立的状态——所以你甚至可以在同一个组件中使用两次相同的自定义Hook。
自定义Hook更像是一种惯例而不是一个功能。假如一个函数命名以“use”开头,并且在函数中调用了其他Hook,那么我们就可以说这是一个自定义Hook。useXXX的命名规则是我们linter插件在使用了Hook的代码中能够发现bug的原因。
你能编写自己的Hook来覆盖很宽泛的场景,像handling,动画,声明式订阅,定时器,甚至是更多我们没有考虑过的。我们也会很兴奋的看到社区中将要到来什么样的自定义Hook。
细节解释: 你能学到更多自定义Hook的内容,尽在“构建自己的Hook”一讲
其他Hook
还有些内建的Hook你可能觉得有用。像useContext让你订阅React上下文context而不用引入嵌套:
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
以及useReducer让你在复杂组件中利用reducer来管理本地状态:
function Todos() {
const [todos, dispatch] = useReducer(todosReducer);
// ...
细节解释 在“Hooks API 引用”中你能了解更多
下一步
好嘛,真快!如果现在感到有些没意义,或者你希望了解更多细节,那么你可以阅读下一讲,就从State Hook文档开始。
你还可以查看HooksAPI参考和HooksFAQ。
最后,不要错过介绍页面,它解释了我们为什么要添加Hooks,以及我们如何开始使用Hooks与class一同工作——而不是重写我们的应用程序。