Hooks
假如你的组件需要记住下拉菜单是否打开,或者需要在渲染后运行一些代码来与服务器同步数据。这两种情况都需要用到 Hook。你已经用过其中两个了:useState 让组件拥有记忆能力,useEffect 在渲染后运行代码来做 React 之外的事情。这两个是你用得最多的,其他内置 Hook 的用法也是一样的:你在组件内调用一个函数,它就把你接入 React 的某个功能。这个函数的名字总是以 use 开头,这个前缀就是 Hook 的全部定义。
常用内置 Hook 简览
你不需要在第一天就掌握所有 Hook,但知道有哪些工具可用很有帮助,这样当遇到问题时你就能认出合适的工具。以下是最常用的几个:
useRef保存一个可变的值,在渲染之间保留这个值,不会触发新的渲染。它也是你获取 DOM 元素直接引用的方式,比如聚焦一个输入框。useContext从周围的 provider 读取共享数据,这样一个深层嵌套的组件就不用通过每一层都传递 props 了。useReducer通过 reducer 函数来管理状态,每次更新描述一个从当前状态到下一个状态的转换。当状态变化变得复杂到单个useState开始吃力的时候,这个很好用。useMemo在渲染之间缓存一个计算值,只在它的输入改变时才重新计算。useCallback对函数做同样的事,在渲染之间返回同一个函数实例,而不是每次都产生一个新的。
useMemo 和 useCallback 会在后面有真正的性能故事要讲时再深入讨论。Refs and the DOM 会完整讲解 useRef。对于 useContext 和 useReducer,这章的内容是最深入的,有需要的时候就回来查看。现在的要点是这个模式:Hook 是你进入某个 React 功能的方式,它的名字告诉你这是一个 Hook。
Hook 的规则
Hook 有两条规则,React 依赖你遵守它们。
首先,在组件的顶层调用 Hook。不要在条件、循环或嵌套函数里调用它们。组件使用的每个 Hook 都应该在每次渲染时以相同的顺序运行。
function Profile({ isLoggedIn }) {
// 错误:这个 Hook 只在某些时候运行
if (isLoggedIn) {
const [name, setName] = useState('')
}
// 正确:在顶层无条件地调用它
const [name, setName] = useState('')
// ...然后在你的逻辑里使用 isLoggedIn
}其次,只在 React 函数组件或其他自定义 Hook 里调用 Hook。普通的 JavaScript 函数不能使用 Hook,因为 React 没有地方来附加这个状态。
编写自己的 Hook
假如多个组件都需要知道浏览器窗口的宽度。与其在每个组件里复制同样的 useState 和 useEffect 代码,不如把这个逻辑封装一次:
import { useState, useEffect } from 'react'
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return width
}现在任何组件都能调用 const width = useWindowWidth() 并获得一个会随窗口大小变化而更新的值。useWindowWidth 是一个自定义 Hook:一个调用其他 Hook 的函数。就是这样一个模式,因为它是一个函数,你可以给一段有状态的逻辑起个名字,把它写在一个地方,然后在多个组件间重用它。use 前缀是把它标记为 Hook 的方式,这样 React 的工具和读你代码的人都能认出来。自定义 Hook 把你已有的 Hook 打包成可重用的东西;它们不会给 React 增加新的能力。
use 开头的函数,它把你的组件接入 React 的某个功能,比如内存或副作用。你已经遇过 useState 和 useEffect 了,其他的也是一样的形式。最重要的是:在组件的顶部调用 Hook,永远不要在 if 或循环里,这样它们在每次渲染时都以相同的方式运行。 下一步:Thinking in React,这是关于组件、props、状态和副作用如何联合起来把设计变成 UI 的过程。

