如何编写自定义 Hooks
如何编写自定义 Hooks
自定义 Hook 是把组件里可复用的「状态 + 副作用」逻辑抽成以 use 开头的函数。它本身不是新特性,而是对已有 Hooks 的组合与封装,便于在多个组件间共享行为。
命名与约定
- 函数名必须以
use开头,例如useWindowSize、useFetch。 - 内部可以调用
useState、useEffect等任意 Hooks。 - 不要在普通工具函数里偷偷调用 Hooks;Hooks 只能写在自定义 Hook 或组件顶层。
这样命名也方便 ESLint 的 eslint-plugin-react-hooks 做规则检查。
最小示例:封装窗口宽度
import { useState, useEffect } from "react";
export function useWindowWidth() {
const [width, setWidth] = useState(() =>
typeof window !== "undefined" ? window.innerWidth : 0,
);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return width;
}
在组件中使用:
function Header() {
const width = useWindowWidth();
return <span>当前宽度:{width}px</span>;
}
带参数的 Hook:根据 key 拉取数据
import { useState, useEffect } from "react";
export function useJson(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
fetch(url)
.then((res) => {
if (!res.ok) throw new Error(res.statusText);
return res.json();
})
.then((json) => {
if (!cancelled) setData(json);
})
.catch((e) => {
if (!cancelled) setError(e);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [url]);
return { data, error, loading };
}
url 变化会重新请求;卸载或 url 快速切换时用 cancelled 避免把旧请求结果写回 state。
返回值设计
常见两种风格,按团队习惯统一即可:
| 风格 | 示例 | 说明 |
|---|---|---|
| 元组 | return [value, setValue] | 类似 useState,调用处解构简短 |
| 对象 | return { data, error, loading } | 字段多时可读性更好,扩展方便 |
不要踩的坑
条件里调用 Hooks
自定义 Hook 内部也必须在顶层调用useState/useEffect,不能写在if (x) { useEffect(...) }里。把「非共享」逻辑硬抽
只用一次的简单逻辑不必强行抽 Hook,避免过度抽象。闭包拿到过期的 state
若在setInterval、事件回调里读 state,注意依赖与useEffect依赖数组 是否完整,必要时用函数式更新或useRef存最新值。SSR
访问window、document时先判断环境或放在useEffect里,避免服务端报错。
与「高阶组件 / render props」对比
自定义 Hook 通常更直观:无额外组件嵌套,逻辑与 UI 分离清晰。旧项目里 HOC、render props 仍会遇到,新代码优先 Hooks 即可。
小结
- 自定义 Hook = 以
use开头的函数 + 内部组合官方 Hooks。 - 适合抽离:订阅、请求、浏览器尺寸、本地存储、防抖节流等跨组件逻辑。
- 注意依赖、清理函数、竞态,与在组件里写
useEffect时相同。
更多模式可参考 React 文档:自定义 Hook。
