在 React 中,Hook 为函数组件带来了状态与生命周期管理能力。而当你在多个组件中反复写相似逻辑时,自定义 Hook 能将这部分逻辑抽象出来,提高代码复用性与可读性。下面,我们一起探索自定义 Hook 的原理、步骤、示例与实践建议。
自定义 Hook 的核心理念与作用
复用状态逻辑:当不同组件中需要共享某种状态或副作用时,自定义 Hook 可以封装这些逻辑,避免重复编写。
解耦组件与业务逻辑:组件负责界面渲染,自定义 Hook 管理内部状态、网络请求或订阅逻辑,让组件更专注展示层。
隔离副作用与结构清晰:可以把复杂的 useEffect、清理函数、状态控制等放入 Hook 中,使组件主体结构更简洁。
提升测试性:自定义 Hook 可以单独编写测试,隔离业务逻辑与组件渲染。
自定义 Hook 其实就是一个普通的函数,但它内部可以调用 React 的其他 Hook(如 useState、useEffect 等)。它本质上是“可复用的带状态逻辑单元”。
命名规范与规则
在创建自定义 Hook 时,要遵循以下规则:
函数名称必须以 use 开头,比如 useFetch、useToggle、useAuth。
Hook 内部只能调用其他 Hook,不能在条件分支、循环或嵌套函数中调用 Hook(必须遵守 Hook 的调用顺序规则)。
自定义 Hook 只封装状态逻辑,不应该渲染 UI(即不返回 JSX,而是返回数据、状态或操作函数)。
这些规范保证了 Hook 的可预测性与正确性。
自定义 Hook 的基本步骤与示例
下面是一个从最基础到稍复杂的几个示例:
示例 1:useFetch — 封装网络请求
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
setLoading(true);
setError(null);
fetch(url)
.then(res => {
if (!res.ok) throw new Error("Network response was not ok");
return res.json();
})
.then(json => {
setData(json);
})
.catch(err => {
setError(err);
})
.finally(() => {
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
使用方式:
function MyComponent() {
const { data, loading, error } = useFetch("/api/users");
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{JSON.stringify(data)}</div>;
}
这个 Hook 把请求逻辑、错误处理、加载状态都抽离出来,多个组件可以共享。
示例 2:usePrevious — 获取上一次的值
import { useEffect, useRef } from "react";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
在组件中使用:
function Counter({ count }) {
const prev = usePrevious(count);
return <p>当前:{count}, 上一次:{prev}</p>;
}
这个 Hook 利用了 useRef 在重渲染之间保存一个“旧值”。
示例 3:useDebounce — 防抖 Hook
import { useState, useEffect } from "react";
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebounced(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debounced;
}
用在输入框等场景中,可以减少频繁请求或处理。
何时应该创建自定义 Hook
创建自定义 Hook 的场景通常包括:
- 某段状态逻辑、网络请求、订阅逻辑在多个组件中重复出现。
- 组件过于臃肿,需要将副作用、状态处理抽离出来。
- 想提高代码可测试性、可读性,希望将复杂逻辑封装成单元。
- 逻辑之间有明确边界,可以形成独立职责模块。
但也要避免过度抽象:若逻辑只在一个组件中出现,或抽象后反而使接口混乱,就不一定要提取为 Hook。
最佳实践与注意事项
- Hook 应尽量“小而专一”,一个 Hook 只处理一种职责(如网络请求、节流、订阅)。
- 保持 Hook 的依赖声明准确。不要遗漏 useEffect 或 useMemo 的依赖项。
- 在 Hook 中返回清晰的接口:通常是一个对象或数组,包含状态 + 操作函数。
- 在 Hook 中处理必要的清理(如取消订阅、清除超时、取消请求)。
- 给 Hook 添加参数控制行为(如依赖、开关、触发条件),增加灵活性。
- 编写针对 Hook 的单元测试,将逻辑与组件渲染分离。
总结
自定义 Hook 是 React 为逻辑复用而提供的重要机制。通过将状态、异步、订阅等逻辑封装在规则明确的 Hook 中,组件变得干净、职责单一,同时降低重复代码与提高可维护性。在实际项目中,合理设计与抽象自定义 Hook,对提升代码质量与开发效率具有显著帮助。