Hooks 概述

Hooks 是一项新功能提案,可让您在不编写类的情况下使用 state(状态) 和其他 React 功能。它们目前处于 React v16.7.0-alpha 中,并在 一个开放RFC 中进行讨论。

Hooks 是向后兼容的。这个页面是提供给有 React 经验用户的 Hooks 概述。

这是一个快节奏的概述。如果你感到困惑,请留意我们黄色框中的提示:

详细说明

阅读 动机(Motivation) 以了解我们为何将 Hooks 引入到 React 中。

↑↑↑ 每个部分都以这样的黄色框结束。 它们链接到详细的解释。

📌 State(状态) Hook

此示例渲染一个计数器。单击该按钮时,它会递增值:

import { useState } from 'react';

function Example() {
  // 声明一个新的状态变量,我们将其称为 "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(我们将在稍后讨论这意味着什么)。 我们在函数组件中调用它来向它添加一些本地 state(状态) 。 React 将在重新渲染之间保留此 state(状态) 。 useState 返回一对:current state(状态)值和一个允许你更新它的函数。 您可以从事件处理程序或其他位置调用此函数。 它类似于类中的 this.setState ,除了它不会将旧 state(状态) 和新 state(状态) 合并在一起。 (我们将在 使用 State(状态) Hook 中显示一个 useStatethis.state 进行比较的示例。)

useState 的唯一参数是初始 state(状态)。 在上面的例子中,它是 0,因为我们的计数器从零开始。 请注意,与 this.state 不同,此处的状态不必是对象 - 尽管如果你想要的话也可以是。 初始状态参数仅在第一次渲染期间使用。

声明多个 state(状态) 变量

您可以在单个组件中多次使用 State(状态) Hook:

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

数组解构 语法允许我们为通过调用 useState 声明的 state(状态) 变量赋值给不同的名称。 这些名称不是 useState API的一部分。 相反,React 假定如果多次调用 useState ,则在每次渲染期间以相同的顺序执行。 我们将回到为什么这种方法有效以及何时有用。

但是什么是 Hook 呢?

Hooks 是一种函数,该函数允许您从函数式组件 “勾住(hook into)” React 状态和生命周期功能。 Hooks 在类内部不起作用 - 它们允许你无需类就使用 React。 (我们 不建议 你马上开始重写你现有的组件,但你可以在新组件中开始使用 Hook。)

React 提供了一些像 useState 这样的内置 Hook 。 您还可以创建你自己的 Hook 以在不同组件之间重用有状态的行为。 我们先来看看内置的Hooks。

详细说明

您可以在专用页面上了解有关 State(状态) Hook 的更多信息:使用 State(状态) Hook

⚡️ Effect Hook

你之前很可能已经执行了数据提取、订阅、或手工改变来自 React 组件的 DOM。我们称这些操作为“副作用(side effect)”,因为它们会影响其他组件,并且在渲染过程中无法完成。

Effect Hook,useEffect,增加了从函数式组件执行副作用的功能。它与 React 类中的 componentDidMountcomponentDidUpdate,和 componentWillUnmount 有相同的功能,但是统一为单个 API。(我们将展示一个例子,该示例在 使用 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 Hook 中更多地讨论这与的类组件的生命周期之间的比较。)

Effects 还可以通过返回一个函数来指定它们之后如何“清理(clean up)”。例如,此组件使用 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 允许您根据组件的相关部分(例如添加和删除订阅)来组织组件中的副作用,而不是强制基于生命周期方法进行拆分。

详细说明

您可以在专用页面上了解有关 useEffect 的更多信息:使用 Effect Hook

✌️ Hook 的规则

Hook 是 JavaScript 函数,但强加了两个额外的规则:

  • 只能在顶层调用 Hook,不要在循环、条件或嵌套函数中调用 Hook。
  • 仅从 React 函数式组件中调用 Hook。不要从常规 JavaScript 函数调用 Hook。(还有另一个有效的地方来调用 Hook,即你的自定义 Hook。)

我们提供了一个linter 插件 来自动强制执行这些规则,这些规则乍看起来有些令人疑惑,但它们对 Hook 的良好运行至关重要。

详细说明

您可以在专用页面上了解有关这些规则的更多信息:Hooks 规则

💡 构建你自己的 Hooks

有时,我们希望在组件之间重用一些 有状态(stateful) 逻辑。 传统上,这个问题有两个流行的解决方案:高阶组件渲染属性(props)。 自定义 Hooks 允许您执行这样的操作,但无需向树中添加更多组件。

在本页前面,我们介绍了一个调用 useStateuseEffect Hooks 的 FriendStatus 组件来订阅朋友的在线状态。 假设我们还希望在另一个组件中重用此订阅逻辑。

首先,我们将这个逻辑提取到一个名为 useFriendStatus 的自定义 Hook 中:

import { 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>
  );
}

这些组件的 state(状态) 是完全独立的。 Hooks 是重用 有状态逻辑 的一种方式,而不是 state(状态) 本身。 事实上,每次调用 Hook 都有一个完全隔离的 state(状态) - 所以你甚至可以在一个组件中多次使用相同自定义的 Hook 。

自定义 Hooks 更像是一种约定而非功能。 如果函数的名称以 “use” 开头并且它调用其他 Hook ,我们就可以说它是一个自定义 Hook 。 useSomething 命名约定可以帮助我们的 linter 插件在使用钩子代码中找到 bug 。

您可以编写自定义 Hook ,涵盖广泛的用例,如表单处理,动画,声明订阅,计时器,以及可能还有更多我们没有考虑到的。 我们很高兴看到 React 社区提出的自定义 Hooks。

详细说明

您可以在专用页面上了解有关自定义 Hook 的更多信息:构建你自己的 Hooks

🔌 其他 Hooks

您可能会发现一些不太常用的内置 Hook ,但是很有用。 例如,useContext 允许您订阅 React context 而不引入嵌套:

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

还有,useReducer 允许您使用 reducer 管理复杂组件的本地 state(状态) :

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

详细说明

您可以在专用页面上了解有关所有内置 Hook 的更多信息:Hooks API 参考

下一步

节奏是不是太快了,如果有些事情没有意义,或者您想详细了解更多信息,可以阅读下一页,从 State(状态) Hook 文档开始。

您还可以查看 Hooks API参考Hooks FAQ

最后,不要错过 介绍页面 ,它解释了 为什么 我们正在添加 Hooks 以及我们将如何开始将它们与类并行使用 - 而无需重写我们的应用程序。