自定义 Hook
聊聊这个让代码复用到飞起的神器
先说结论:自定义 Hook 就是个普普通通的 JavaScript 函数,只不过名字得用 use 开头,里面可以调用其他 Hook 而已。
但别小看这个"只不过",它可是 React 里逻辑复用的终极武器。
自定义 Hook 能干啥?
想象一下:你在好几个组件里写了类似的代码——都是监听窗口大小、都是处理表单输入、都是请求接口数据……
这时候你就可以把这部分逻辑抽出来,做成一个自定义 Hook。
// 抽出来的自定义 Hook
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handleResize = () => {
setSize({ width: window.innerWidth, height: window.innerHeight });
};
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
}
然后在组件里直接用:
function MyComponent() {
const { width, height } = useWindowSize();
return (
<div>
窗口大小: {width} x {height}
</div>
);
}
好处就是:
- 组件只需要专注渲染 UI,逻辑都丢给 Hook
- 代码更清晰,改逻辑不用满世界找
- 多个组件想用直接调用,复制粘贴再见
必须遵守的规则
1. 名字必须以use 开头
这不是建议,是强制的。
React 的 ESLint 插件就靠这个 use 前缀来检测你是不是在正确的地方调用 Hook。没有这个前缀,React 才不管你内部调用了什么,直接把你当普通函数处理,规则检查全失效,bug 就找上门了。
// ❌ 错误:没有 use 前缀
function getWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth });
return size;
}
// ✅ 正确:带 use 前缀
function useWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth });
return size;
}
2. 只搞逻辑,别搞 UI
这是自定义 Hook 和组件的根本区别:
- 组件:负责渲染 UI
- 自定义 Hook:负责带状态的逻辑
所以你的 Hook 应该返回的是值和方法,而不是 JSX:
// ❌ 错误:返回了 JSX,这应该是组件该干的事
function useButton() {
return <button>点我</button>;
}
// ✅ 正确:只返回状态和操作
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
3. 每个组件调用,都是独立的状态
这点很重要:你在两个不同组件里调用同一个自定义 Hook,它们的状态是独立的,互不影响。
function A() {
const [count, setCount] = useCounter(); // 这是 A 的 count
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
function B() {
const [count, setCount] = useCounter(); // 这是 B 的 count,跟 A 没关系
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
自定义 Hook 只是复用了一段代码逻辑,而不是共享状态本身。除非你在 Hook 里接了 Redux、Zustand 这些全局状态库。
怎么写好一个自定义 Hook?
1. 单一职责,一个 Hook 只干一件事
// ❌ 一个 Hook 干了两件事
function useUserData() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
// ...
}
// ✅ 拆成两个,各干各的
function useUserData() {
/* 用户数据相关 */
}
function useTheme() {
/* 主题相关 */
}
一个 Hook 做得越小越好,用的时候想组合就组合,灵活得很。
2. 输入输出要清晰
- 参数:像普通函数一样,通过参数传入配置
- 返回值:通常返回数组
[value, setValue] 或者对象 { data, loading, error }
// 返回数组 - 简单场景
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
return [value, () => setValue((v) => !v)];
}
// 返回对象 - 返回内容多的时候
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// ...
return { data, loading, error };
}
3. 方便测试
因为 Hook 不涉及 UI,纯逻辑,所以特别容易写单元测试。
// 测试可以这样写
const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(11);
不用渲染 DOM,直接测逻辑,干净利落。
总结一下
- 自定义 Hook = 普通函数 + 必须用 use 开头 + 里面能调 Hook
- 作用:把带状态的逻辑抽出来复用
- 规则:名字带 use、只返回逻辑不返回 UI、每次调用状态独立
- 技巧:一个 Hook 只做一件事、输入输出要清晰、记得写测试