原文:Hooks at a Glance

Hooks是React 16.8的新特性。这些特性让你不必专门写class组件,来使用state和其他React特性。
_
Hooks向后兼容。这一讲是为有经验的React用户提供的Hooks概览。是一个快熟浏览。如果你迷惑不解,就看这样的黄框提示:

细节解释 阅读动机来学习为什么我们介绍React Hooks

每一小节结束都会有这个,它们链接到相关细节。

State Hook

下例渲染了一个计数器,当你点击button,就会增加计数:

  1. import React, { useState } from 'react';
  2. function Example() {
  3. // Declare a new state variable, which we'll call "count"
  4. const [count, setCount] = useState(0);
  5. return (
  6. <div>
  7. <p>You clicked {count} times</p>
  8. <button onClick={() => setCount(count + 1)}>
  9. Click me
  10. </button>
  11. </div>
  12. );
  13. }

这里,useState就是一个Hook(我们稍后会讨论它的意思)。我们调用它在一个函数组件中并给组件加上了本地状态。React会持有这个状态在每次重复渲染之间。useState返回一对值:当前的状态值以及一个让你更新当前状态值的函数。你能在事件处理函数或者其他地方随便使用这个函数。就像this.setState在class中一样,只是它不会将新旧状态合并在一起(我们在使用State Hook这一讲中会对比useState和this.state的区别)。
useState的唯一函数是设置初始值。在上面的例子中,0就是计数器开始的值。注意这不像this.state,这里的state不是一个对象object——如果你想是object也是可以的。初始状态就在第一次渲染时使用一次。

声明多个状态变量

你可以在统一组件中使用state hook不止一次:

  1. function ExampleWithManyStates() {
  2. // Declare multiple state variables!
  3. const [age, setAge] = useState(42);
  4. const [fruit, setFruit] = useState('banana');
  5. const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  6. // ...
  7. }

数组结构语法让我们给通过调用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:

  1. import React, { useState, useEffect } from 'react';
  2. function Example() {
  3. const [count, setCount] = useState(0);
  4. // Similar to componentDidMount and componentDidUpdate:
  5. useEffect(() => {
  6. // Update the document title using the browser API
  7. document.title = `You clicked ${count} times`;
  8. });
  9. return (
  10. <div>
  11. <p>You clicked {count} times</p>
  12. <button onClick={() => setCount(count + 1)}>
  13. Click me
  14. </button>
  15. </div>
  16. );
  17. }

当你调用useEffect,你就在告诉React在刷完DOM的变化后调用你的副作用函数。effects副作用是在组件内部声明的,所以它能访问props以及state。默认的是,React每次渲染都会运行这些effects副作用——包括第一次渲染。(我们将在后面课程中对比生命周期方法)
Effects副作用也也可以通过返回一个函数来明确之后怎样被清除。比如说,这个组件使用了effect副作用来订阅朋友的在线状态,以及通过取消订阅来清除:

  1. import React, { useState, useEffect } from 'react';
  2. function FriendStatus(props) {
  3. const [isOnline, setIsOnline] = useState(null);
  4. function handleStatusChange(status) {
  5. setIsOnline(status.isOnline);
  6. }
  7. useEffect(() => {
  8. ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  9. return () => {
  10. ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  11. };
  12. });
  13. if (isOnline === null) {
  14. return 'Loading...';
  15. }
  16. return isOnline ? 'Online' : 'Offline';
  17. }

在这个例子中,React将通过ChatAPI取消订阅,当组件卸载的时候,但同时也在卸载前,在一系列随后渲染期间重新运行这个effect副作用。(在“使用Effect Hook”中有教你让React在this.props.firend没有改变的时候,不要重新订阅的方法)
正如useState,你可以在组件中写不止一个副作用:

  1. function FriendStatusWithCounter(props) {
  2. const [count, setCount] = useState(0);
  3. useEffect(() => {
  4. document.title = `You clicked ${count} times`;
  5. });
  6. const [isOnline, setIsOnline] = useState(null);
  7. useEffect(() => {
  8. ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  9. return () => {
  10. ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  11. };
  12. });
  13. function handleStatusChange(status) {
  14. setIsOnline(status.isOnline);
  15. }
  16. // ...

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:

  1. import React, { useState, useEffect } from 'react';
  2. function useFriendStatus(friendID) {
  3. const [isOnline, setIsOnline] = useState(null);
  4. function handleStatusChange(status) {
  5. setIsOnline(status.isOnline);
  6. }
  7. useEffect(() => {
  8. ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
  9. return () => {
  10. ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
  11. };
  12. });
  13. return isOnline;
  14. }

它把friendID作为参数,返回朋友在线的状态。
现在我们在多个组件中使用它:

  1. function FriendStatus(props) {
  2. const isOnline = useFriendStatus(props.friend.id);
  3. if (isOnline === null) {
  4. return 'Loading...';
  5. }
  6. return isOnline ? 'Online' : 'Offline';
  7. }
  1. function FriendListItem(props) {
  2. const isOnline = useFriendStatus(props.friend.id);
  3. return (
  4. <li style={{ color: isOnline ? 'green' : 'black' }}>
  5. {props.friend.name}
  6. </li>
  7. );
  8. }

这些组件的状态是完全独立的。Hooks是一种复用有状态逻辑的方法,但不是状态本身。实际上,每一次调用Hook就一个独立的状态——所以你甚至可以在同一个组件中使用两次相同的自定义Hook。
自定义Hook更像是一种惯例而不是一个功能。假如一个函数命名以“use”开头,并且在函数中调用了其他Hook,那么我们就可以说这是一个自定义Hook。useXXX的命名规则是我们linter插件在使用了Hook的代码中能够发现bug的原因。
你能编写自己的Hook来覆盖很宽泛的场景,像handling,动画,声明式订阅,定时器,甚至是更多我们没有考虑过的。我们也会很兴奋的看到社区中将要到来什么样的自定义Hook。

细节解释: 你能学到更多自定义Hook的内容,尽在“构建自己的Hook”一讲

其他Hook

还有些内建的Hook你可能觉得有用。像useContext让你订阅React上下文context而不用引入嵌套:

  1. function Example() {
  2. const locale = useContext(LocaleContext);
  3. const theme = useContext(ThemeContext);
  4. // ...
  5. }

以及useReducer让你在复杂组件中利用reducer来管理本地状态:

  1. function Todos() {
  2. const [todos, dispatch] = useReducer(todosReducer);
  3. // ...

细节解释 在“Hooks API 引用”中你能了解更多

下一步

好嘛,真快!如果现在感到有些没意义,或者你希望了解更多细节,那么你可以阅读下一讲,就从State Hook文档开始。
你还可以查看HooksAPI参考和HooksFAQ。
最后,不要错过介绍页面,它解释了我们为什么要添加Hooks,以及我们如何开始使用Hooks与class一同工作——而不是重写我们的应用程序。