自定义 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 出场了。

笔记加载中…