自定义 Hook
搜索、防抖的逻辑都写死在组件里,换个组件想用只能复制粘贴。React 允许把"带状态的逻辑"抽成可复用函数,只要函数名以 use 开头,它就是一个自定义 Hook。本章把上一章的防抖搜索从组件中抽出来,封装成自定义 Hook。
自定义 Hook 的规则
自定义 Hook 本质上还是普通函数,只有两条规则:
- 名字必须以 use 开头,React 靠命名来识别 Hook,以便帮你检查使用规则。
- 函数内部可以调用 useState、useEffect 等官方 Hook。
返回什么由你自己决定:返回一个值、一个数组或一个对象都可以。
抽出 useDebounce
先做最通用的防抖 Hook:传入一个"变化很快"的值,返回"停顿之后才稳定"的值:
// src/hooks/useDebounce.js
import { useEffect, useState } from "react";
export default function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer); // value 一变就取消上一个定时器
}, [value, delay]);
return debounced;
}
在组件里使用
组件里调用 useDebounce,拿到防抖后的关键字再去请求,输入再快也不会频繁触发:
import { useEffect, useState } from "react";
import useDebounce from "./hooks/useDebounce";
function SearchPosts() {
const [keyword, setKeyword] = useState("");
const debouncedKeyword = useDebounce(keyword); // 停顿 300ms 后的稳定关键字
useEffect(() => {
if (debouncedKeyword) fetchPosts(debouncedKeyword).then(setPosts);
}, [debouncedKeyword]);
return <input value={keyword} onChange={(e) => setKeyword(e.target.value)} />;
}
组件里几乎看不到防抖细节——逻辑被封装后,代码一下子清爽了。
抽出 usePosts
再把"按关键字请求文章"也抽成 Hook,返回 { posts, loading },任何页面一行调用即可拿到数据:
// src/hooks/usePosts.js
import { useEffect, useState } from "react";
export default function usePosts(keyword) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetchPosts(keyword).then((data) => {
setPosts(data);
setLoading(false);
});
}, [keyword]);
return { posts, loading };
}
首页、搜索页各调一次 usePosts,两份状态是相互独立的,互不干扰。
使用注意
自定义 Hook 内部同样要遵守 Hooks 规则:不能写在 if/for 等条件分支里调用官方 Hook,否则每次渲染时 Hook 的调用顺序不一致,React 就无法对上号。
小结
自定义 Hook 用 use 开头封装"状态 + 副作用"逻辑,让组件只专注界面。防抖、请求、定时器这类逻辑从此可以一处编写、处处复用。当很多组件要共享同一份数据时,就该轮到 Context 出场了。