### 彪哥

爱好前端

- 核心概念

- 高级指引

- Hook

  - Hook概述
    - 什么是 Hook?
    - useState（状态钩子）
      - 声明多个 state 变量
    - Effect Hook (副作用钩子)
      - useEffect（生命周期钩子）
      - “清除”副作用
    - Hook 使用规则
    - 自定义 Hook （Hook的使用逻辑封装）
    - 其他 Hook
  - 使用State Hook
  - 使用Effect Hook
  - Hook规则
  - 自定义 Hook

- 案例演示

- 《React》笔记
- Hook

heBody
2021-03-31
目录

# Hook概述

# # 01. Hook概述

Hook可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

## # 什么是 Hook?

Hook 是一个特殊的函数，可以让你在函数组件里“钩入” React state 及 生命周期 等特性的函数。

> 1. Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

> 2. React 内置了一些像 `useState` 这样的 Hook。你也可以创建你自己的 Hook 来复用不同组件之间的状态逻辑。我们会先介绍这些内置的 Hook。

## # useState（状态钩子）

模式：

    const [<取值>, <设值>] = useState(<初始值>)

1

上面的三个值均可自定义名称，分别是：

- state取值变量名
- state设值方法名
- 初始值
  - 初始值可以是数字、字符串、对象、数组等

例子：

    import React, { useState } from 'react';

    function Example() {
      // 声明一个新的叫做 “count” 的 state 变量
      const [count, setCount] = useState(0);

      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

### # 声明多个 state 变量

    function ExampleWithManyStates() {
      // 声明多个 state 变量！
      const [age, setAge] = useState(42);
      const [fruit, setFruit] = useState('banana');
      const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
      // ...
    }

1
2
3
4
5
6
7

## # Effect Hook (副作用钩子)

你之前可能已经在 React 组件中执行过数据获取、订阅 或 者手动修改 DOM。我们统一把这些操作称为“副作用”，或者简称为“作用”。

### # useEffect（生命周期钩子）

`useEffect` 就是一个 Effect Hook，给函数组件增加了操作副作用的能力。它跟 class 组件中的 `componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount` 具有相同的用途，只不过被合并成了一个 API。（我们会在使用 Effect Hook
(opens new window) 里展示对比 `useEffect` 和这些方法的例子。）

例如，下面这个组件在 React 更新 DOM 后会设置一个页面标题：

    import React, { useState, useEffect } from 'react';
    function Example() {
      const [count, setCount] = useState(0);

      // 相当于 componentDidMount 和 componentDidUpdate:
      useEffect(() => {
        // 第一次渲染DOM以及更新DOM后执行此函数
        // 使用浏览器的 API 更新页面标题
        document.title = `You clicked ${count} times`;
      });
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

当你调用 `useEffect` 时，就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的，所以它们可以访问到组件的 props 和 state。默认情况下，React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。

### # “清除”副作用

副作用函数还可以通过返回一个函数来指定如何“清除”副作用。例如，在下面的组件中使用副作用函数来订阅好友的在线状态，并通过取消订阅来进行清除操作：

    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';
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在这个示例中，React 会在组件销毁时取消对 `ChatAPI` 的订阅，然后在后续渲染时重新执行副作用函数。（如果传给 `ChatAPI` 的 `props.friend.id` 没有变化，你也可以告诉 React 跳过重新订阅
(opens new window)。）

跟 `useState` 一样，你可以在组件中多次使用 `useEffect` ：

    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);
      }
      // ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

通过使用 Hook，你可以把组件内相关的副作用组织在一起（例如创建订阅及取消订阅），而不要把它们拆分到不同的生命周期函数里。

> 详细说明

> 你可以在这一章节了解更多关于 `useEffect` 的内容：使用 Effect Hook
> (opens new window)

## # Hook 使用规则

Hook 就是 JavaScript 函数，但是使用它们会有两个额外的规则：

- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。（还有一个地方可以调用 Hook —— 就是自定义的 Hook 中，我们稍后会学习到。）

同时，我们提供了 linter 插件
(opens new window)来自动执行这些规则。这些规则乍看起来会有一些限制和令人困惑，但是要让 Hook 正常工作，它们至关重要。

> 详细说明

> 你可以在这章节了解更多关于这些规则的内容：Hook 使用规则
> (opens new window)。

## # 自定义 Hook （Hook的使用逻辑封装）

有时候我们会想要在组件之间重用一些状态逻辑。目前为止，有两种主流方案来解决这个问题：高阶组件
(opens new window)和 render props
(opens new window)。自定义 Hook 可以让你在不增加组件的情况下达到同样的目的。

前面，我们介绍了一个叫 `FriendStatus` 的组件，它通过调用 `useState` 和 `useEffect` 的 Hook 来订阅一个好友的在线状态。假设我们想在另一个组件里重用这个订阅逻辑。

首先，我们把这个逻辑抽取到一个叫做 `useFriendStatus` 的自定义 Hook 里：

    import React, { useState, useEffect } from 'react';

    // 逻辑抽取，方便复用
    function useFriendStatus(friendID) {
      // 每次调用时的state都是独立的
      const [isOnline, setIsOnline] = useState(null);

      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }

      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
      });

      return isOnline;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

它将 `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>
      );
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

每个组件间的 state 是完全独立的。Hook 是一种复用状态逻辑的方式，它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。

自定义 Hook 更像是一种约定而不是功能。如果函数的名字以 “`use`” 开头并调用其他 Hook，我们就说这是一个自定义 Hook。 `useSomething` 的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。

你可以创建涵盖各种场景的自定义 Hook，如表单处理、动画、订阅声明、计时器，甚至可能还有更多我们没想到的场景。我们很期待看到 React 社区会出现什么样的自定义 Hook。

> 详细说明

> 我们会在这一章节介绍更多关于自定义 Hook 的内容： 创建你自己的 Hook
> (opens new window)。

## # 其他 Hook

除此之外，还有一些使用频率较低的但是很有用的 Hook。比如，`useContext`
(opens new window) 让你不使用组件嵌套就可以订阅 React 的 Context。

    function Example() {
      const locale = useContext(LocaleContext);
      const theme = useContext(ThemeContext);
      // ...
    }

1
2
3
4
5

另外 `useReducer`
(opens new window) 可以让你通过 reducer 来管理组件本地的复杂 state。

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

1
2

> 详细说明

> 你可以在这一章节了解更多关于所有内置 Hook 的内容：Hook API 索引
> (opens new window)。

编辑
(opens new window)
#React
上次更新: 2022/12/14, 19:36:42
高级指引
使用State Hook

← 高级指引 使用State Hook→

最近更新

  01
Git修改分支名
08-11

  02
CSS给table的tbody添加滚动条
06-29

  03
我做了一个手写春联小网页，祝大家虎年暴富 原创
01-28

更多文章>

Copyright © 2016-2022 Hesb | MIT License

- 跟随系统
- 浅色模式
- 深色模式
- 阅读模式