Hook 规则与逻辑组织
Hook 规则与逻辑组织 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
「未登录就不挂这些 Hook」听起来合理,一运行就崩溃。这一课你会理解 Hook 依赖调用顺序的记账原理,修掉条件调用和循环调用,并学会把复杂组件拆成「自定义 Hook + 子组件 + 命名事件处理」的可读结构。
问题场景
课程页第一版:未登录时不渲染标签页,代码顺手把 useState 也包进了条件:
function CoursePage({ user }) {
if (!user) return <p>请先登录</p>; // 提前返回
const [tab, setTab] = useState('overview'); // 崩溃:条件调用 Hook
const [notes, setNotes] = useState([]);
// ...
}
登录瞬间报错:Rendered fewer hooks than expected。未登录那次渲染执行了 0 个 Hook,登录后 React 期望从第 1 个位置找到 tab 的状态——账对不上了。
核心解释
React 给每个组件维护一张按调用顺序记账的表:第一次渲染里第 1 个调用的 Hook 存 tab、第 2 个存 notes……之后每次渲染按同样的顺序取值。只要顺序稳定,位置就是身份。所以规则只有两条:
- 只在顶层调用:不能包在 if、循环、嵌套函数里;提前 return 必须放在所有 Hook 之后。
- 只在函数组件或自定义 Hook 里调用:普通 JS 函数里没有这张账表。
条件需求不是不能有,而是要挪进 Hook 内部:useState(user ? user.id : null)、useEffect(() => { if (!user) return; ... }, [user])——Hook 照常调用,逻辑自己分支。
可读性组织有三板斧,按序使用:状态逻辑按领域抽成自定义 Hook(useCourseTabs、useNotes);事件处理写成命名函数(handleTabChange,而不是内联三十行);渲染分支提炼成子组件。组件函数最终读起来像目录,而不是流水账。
示例拆解
修复版加组织后的结构:
function useCourseTabs(user, initial) {
const [tab, setTab] = useState(initial);
const [notes, setNotes] = useState([]);
useEffect(() => {
if (!user) return; // 条件写在 Hook 内部,Hook 本身照常调用
setNotes([`${user} 的课程笔记`, 'Hook 规则:只在顶层调用']);
}, [user]);
return { tab, setTab, notes }; // 输入 user/initial,输出数据与操作
}
function CoursePage({ user }) {
const { tab, setTab, notes } = useCourseTabs(user, 'overview'); // 顺序永远稳定
if (!user) return <p>请先登录</p>; // 现在提前返回是安全的
function handleTabChange(next) {
setTab(next);
}
return <CourseTabs tab={tab} onChange={handleTabChange} notes={notes} />;
}
- Hook 全部在顶层、顺序固定,
user变化只是让各 Hook 内部走不同分支。 - 提前 return 的位置在所有 Hook 调用之后——新手最容易忽视的细节。
useCourseTabs的输入输出保持函数式接口:传user进去,返回数据与操作,而不是偷偷读外部变量。
动手任务
打开本课实验区:
- 反复点「登录 / 退出登录」,组件在两个分支间往返,确认不崩溃、状态按预期保留或清空。
- 打开实验文件里注释掉的「错误示范」,先口头预测崩溃信息,再复制到本地项目实际运行对照。
- 把实验里
CoursePage的笔记逻辑抽成useNotes(user),组件里只留组合与渲染。
常见错误
- 提前 return 放在 Hook 之前:最常见的事故源;记住「先 Hook,后 return」。
- 在自定义 Hook 里条件调用底层 Hook:包装不豁免规则,问题原样传递。
- 以为
.map()里渲染组件违规:循环里渲染的是组件(各组件各有账表),不是调用 Hook;但items.map(() => useState())这种在当前组件里循环调用 Hook 就是崩溃代码。 - 用「这次渲染碰巧没走到」安慰自己:只要存在走得到的另一条路径,顺序就可能错位,崩溃只是时机问题。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能用「按顺序记账」解释条件调用 Hook 为什么会崩
- 审查组件的固定动作:先扫顶层 Hook 是否无条件执行,再看提前 return 的位置
- 复杂组件已按「自定义 Hook / 命名事件处理 / 子组件」组织,单函数不再超过一屏
参考资料
- React 官方文档:Hook 规则 —— 规则原文与原理
- React 官方文档:用自定义 Hook 复用逻辑 —— 拆分组织的官方教程
- React 官方文档:useState —— 状态槽位行为的 API 侧证
读完这一课了?
标记完成后,工作台会安排接下来的学习。