
抽出来的是逻辑,不是界面
两个组件的界面不同,逻辑却一样:都要一个「网络是否在线」的状态,外加一对事件订阅。抄一遍没必要,抽成函数:
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:它没用到任何 Hookfunction 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 开源库。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时