750 字
4 分钟
自定义 Hook

抽出来的是逻辑,不是界面#

两个组件的界面不同,逻辑却一样:都要一个「网络是否在线」的状态,外加一对事件订阅。抄一遍没必要,抽成函数:

useOnlineStatus.js
import { useState, useEffect } from 'react'
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true)
useEffect(() => {
function handleOnline() {
setIsOnline(true)
}
function handleOffline() {
setIsOnline(false)
}
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
return () => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
}
}, [])
return isOnline
}
function StatusBar() {
const isOnline = useOnlineStatus()
return <h1>{isOnline ? '在线' : '离线'}</h1>
}
function SaveButton() {
const isOnline = useOnlineStatus()
return <button disabled={!isOnline}>{isOnline ? '保存' : '重连中'}</button>
}

组件里只剩「我要在线状态」这层意图,订阅细节收进了函数内部。这是抽自定义 Hook 的主要收益。

命名必须以 use 开头#

官方约定:Hook 名以 use 加一个大写字母开头,组件名以大写字母开头。这条约定让读代码的人一眼看出哪些调用里藏着 state 与 Effect。

linter 也用这条规则做检查。把 useOnlineStatus 改名成 getOnlineStatus,函数里的 useState 与 useEffect 立刻报错。

反过来说,不调用其他 Hook 的函数不该带 use 前缀:

// 不该叫 Hook:它没用到任何 Hook
function useSorted(items) {
return items.slice().sort()
}
// 普通函数,可以在条件里调用
function getSorted(items) {
return items.slice().sort()
}

共享的是逻辑,不是状态#

两次调用 useOnlineStatus(),拿到的是两个各自独立的 state 与 Effect。它们同时变化,只因为订阅的是同一个外部事实。

更直观的对照是两个输入框各自调一次 useFormInput('Mary'):得到两份独立的 state,不是共享的一份。

要在多个组件之间共享同一份状态,做法是把它提升到共同的父组件再往下传,而不是靠自定义 Hook。

每次渲染都会重跑#

自定义 Hook 的代码就是组件体的一部分,组件每次渲染它都跟着跑。两条推论:它必须是纯函数;它总能读到最新的 props 与 state。

这带来一个常见后果:Hook 里接收的回调每次渲染都是新函数,直接放进 Effect 依赖,Effect 会反复重来。官方的解法是把它包成 Effect Event:

import { useEffect, useEffectEvent } from 'react'
export function useInterval(callback, delay) {
const onTick = useEffectEvent(callback)
useEffect(() => {
const id = setInterval(onTick, delay)
return () => clearInterval(id)
}, [delay]) // onTick 不必进依赖
}

onTick 调用时读到的是最新的 callback,而 Effect 只在 delay 变化时重来。细节见 useEffectEvent。

别这么抽#

不要写 useMount 这类按生命周期命名、没有具体目的的 Hook。名字要说明它解决什么问题:useOnlineStatus、useChatRoom、useFormInput。

只在单个组件里用到的逻辑不必抽。抽象要多读一层,收益只在确实复用时才兑现。

需要清理的副作用记得成对写:订阅配退订、计时器配清除、请求配取消。自定义 Hook 里漏了清理,每一个用到它的组件都会出问题。

现成的库可以先用,清单见优质 React Hook 开源库。

第 21 篇,共 39 篇本文属于系列

自定义 Hook
https://www.choria.top/posts/hook-use-custom/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时