大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
useMemo、useCallback 与自定义 Hook
这一课你将能做到
- 说出
useMemo/useCallback是「按需优化」,不是默认必写 - 写出一个最小自定义 Hook(如
useToggle) - 知道自定义 Hook 必须以
use开头的约定
为什么要学
你会在网上看到满屏 useMemo。
入门时更重要的是:先写对,再量是否卡,再优化。
自定义 Hook 则是把「有状态的逻辑」复用出去——开关、倒计时、窗口宽度——这比过早 memo 更值得学。
人话:Hook 就是「能调用其他 Hook 的函数」;你自己写的也算,只要名字以 use 开头。
本站多处开关、抽屉,逻辑相似。复制三份 useState 不如一个 useToggle。
先建立图像
useMemo(() => 计算结果, [依赖]) → 记住某个「值」
useCallback(() => 函数, [依赖]) → 记住同一个「函数引用」
useXxx(...) → 你自己的 Hooks 组合
React 编译器等工具在变强:官方也不再鼓励到处手写 memo。入门把自定义 Hook 写对,比背优化口诀重要。
跟着做
步骤 1:自定义 Hook useToggle(完整可抄)
import { useState, useCallback } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
const setTrue = useCallback(() => setOn(true), []);
const setFalse = useCallback(() => setOn(false), []);
return { on, toggle, setTrue, setFalse };
}
export function Panel() {
const { on, toggle } = useToggle();
return (
<button type="button" onClick={toggle}>
{on ? "开" : "关"}
</button>
);
}
把开关逻辑挪出组件,路径页、设置页都能用。
useCallback 在这里只是让返回的函数引用稳定;没有 React.memo 的子组件时,收益很小,但展示「Hook 里也能用别的 Hook」。
步骤 2:useMemo 示例(理解即可)
import { useMemo, useState } from "react";
export function VisibleList({ items }) {
const [showDone, setShowDone] = useState(true);
const visible = useMemo(() => {
return items.filter((x) => (showDone ? true : !x.done));
}, [items, showDone]);
return (
<>
<button type="button" onClick={() => setShowDone((v) => !v)}>
切换
</button>
<ul>
{visible.map((x) => (
<li key={x.id}>{x.title}</li>
))}
</ul>
</>
);
}
若 filter 很轻、列表很小,直接在渲染里算完全可以。列表极大、计算明显卡顿再 memo。
步骤 3:Hooks 规则(自定义 Hook 同样遵守)
- 只在函数组件或自定义 Hook 顶层调用
- 不要放进 if / for / 嵌套函数
- 每次渲染调用顺序必须一样,React 靠顺序对上上次的 state
步骤 4:自定义 Hook 抽复用
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = () => setOn(v => !v);
return { on, toggle };
}
名称以 use 开头;内部可调其它 Hook。
讲清楚要点
- 自定义 Hook 就是函数,里面可以调
useState/useEffect等。 useCallback单独不会变快;常配合React.memo子组件,避免父渲染导致子因「新函数 props」重绘。入门阶段少纠缠。- 官方在推编译器优化——别把 memo 当信仰。可读性优先。
- 抽 Hook 的时机:同一段 state+事件在两个组件出现,或组件里「开关逻辑」比 JSX 还长。
- 不要把无关状态塞进同一个 Hook,以免复用时拖来一堆用不到的字段。
你可能会懵的地方
-
为何必须叫
useXxx?
约定 + ESLint 靠前缀识别 Hooks。叫toggleHook会漏检规则。 -
可以在 if 里 useState 吗?
不可以。组件和自定义 Hook 都一样。 -
自定义 Hook 会共享 state 吗?
不会。每个调用组件各有一份。要共享用提升或 Context。 -
和普通工具函数差别?
普通函数不能调用 Hooks。纯计算(价格格式化)继续用普通函数即可。 -
先优化还是先对?
先正确。useMemo/useCallback有成本;在测量到卡顿或稳定依赖再加。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 每个变量都 memo | 更难读,未必更快 | 先正确,再量再优化 |
| 复制粘贴同一段 useState+effect | 难维护 | 抽自定义 Hook |
| Hook 里 if 包 useState | 顺序错乱 | 顶层调用 |
| 以为两个组件调同一 Hook 就共享数据 | 各记各的 | 状态提升 / Context |
| 无依据包一层 memo | 噪音 | 有证据再优化 |
| 自定义 Hook 里条件调用 Hook | 破坏规则 | 顶层无条件调用 |
自测三问
- 自定义 Hook 解决什么复用问题?两个组件调用同一 Hook 会共享一份 state 吗?
- 为什么说 useMemo 不是默认标配?
- Hooks 为何不能放在条件语句里?
小结一句话
自定义 Hook 复用有状态逻辑;memo/callback 是优化工具,写对再按需使用。
动手加深
把「收藏开关」抽成 useToggle,两处 UI 复用;确认刷新逻辑仍正确。
跟着做(加厚 · react-hooks-advanced)
步骤:最小可运行对照
在 Vite 或 Next 练习页粘贴本课核心示例,改一个输入,确认屏幕变化符合预期。再故意写一个错法表里的写法,看控制台/界面如何坏,然后改回。
每个概念都问:state 在谁手里?UI 哪一块会因它重绘?
操作顺序:打开对应文件 → 改一处可观察点(文案、类型、className 或分支)→ 保存看结果 → 复原或留下有意义的小改动。
步骤:错法故意练一次
先按「用 jQuery 式直接改 DOM 冒充完成」做 30 秒(或在注释里写出若这样做会怎样),再改成「改数据(state/props)让 UI 推导出来」。 目的不是留坑,而是让失败现象进长期记忆。
你可能会懵的地方(加厚)
-
热更新后状态丢了?
正常:整树重挂会重置 state。用 key 或外提状态保留;开发期别被吓到。 -
和文档示例不一致?
以你锁的 React 大版本 + react.dev 为准;旧博客的 class 组件示例仅作历史阅读。 -
这课和前后课怎么接?
回看大纲上一课的小结一句、下一课标题;用自己的话写「本课产出什么可观察结果」。写不出来就重做跟着做。
错法对照(加厚)
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 jQuery 式直接改 DOM 冒充完成 | 假完成/埋雷 | 改数据(state/props)让 UI 推导出来 |
| 只看不敲 | 第二天全忘 | 至少跑通本课步骤 |
| 跳过自测三问 | 以为会了 | 闭卷答再对稿 |
延伸
- Reusing Logic with Custom Hooks
- 下一课:表单与受控组件。