0Pricing
Frontend Academy · 课时

自定义 Hook:提取可复用逻辑

将有状态逻辑移入自定义的 use* 函数,在组件之间共享行为,避免重复代码或提升状态。

自定义 Hook:提取可复用逻辑 是 CoddyKit 上的免费 Frontend Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Frontend Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Frontend Academy 课程共包含 4 节课。

什么是自定义钩子

自定义钩子是一个名称以 use 开头、并在内部调用其他钩子的 JavaScript 函数。自定义钩子可以提取有状态逻辑,让多个组件共享这些逻辑,而无需复制粘贴。

将逻辑提取到自定义钩子中

找出组件之间重复的逻辑(获取数据、加载状态和错误处理,窗口大小,本地存储),并将其移入自定义钩子。

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);

  return width;
}

// Usage:
const width = useWindowWidth();
const isMobile = width < 768;

useFetch——数据获取钩子

一种可复用的数据获取钩子,任何组件都可以使用它从 URL 加载数据。

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    async function load() {
      try {
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error(`${res.status}`);
        setData(await res.json());
      } catch (err) {
        if ((err as DOMException).name !== 'AbortError')
          setError((err as Error).message);
      } finally { setLoading(false); }
    }
    load();
    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

useLocalStorage 钩子

一种自定义钩子,可以将值同步到 localStorage,并通过存储事件保持不同标签页之间的数据同步。

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  const set = useCallback((newValue: T) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  }, [key]);

  return [value, set] as const;
}

自定义钩子共享逻辑,而不是状态

每个调用自定义钩子的组件都会获得自己独立的状态。钩子不是单例——它们只是用于创建相互隔离状态实例的函数调用。

命名约定

自定义钩子的名称必须以 use 开头。ESLint 的 react-hooks 代码检查规则会强制执行这一点,并确保钩子在顶层且按照正确顺序调用。

返回多个值

自定义钩子通常返回一个对象(包含命名值)或一个元组(类似 useState)。元组允许使用自定义名称进行解构;对象则能更清晰地说明每个值的含义。

// Tuple (like useState):
const [value, setValue] = useLocalStorage('key', 0);

// Object (more descriptive):
const { data, loading, error, refetch } = useFetch('/api/users');

useDebounce 钩子

对值进行防抖:只有在用户停止输入指定的延迟时间后,才更新该值。

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// Usage:
const debouncedSearch = useDebounce(searchTerm, 300);
useEffect(() => { search(debouncedSearch); }, [debouncedSearch]);

useMediaQuery 钩子

将 window.matchMedia 封装到钩子中,以响应式地读取 CSS 媒体查询结果。

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(() => window.matchMedia(query).matches);

  useEffect(() => {
    const mq = window.matchMedia(query);
    const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

测试自定义钩子

使用 @testing-library/react 的 renderHook() 工具,可以在不将自定义钩子包装到组件中的情况下单独测试它。

import { renderHook, act } from '@testing-library/react';

test('useCounter increments', () => {
  const { result } = renderHook(() => useCounter(0));
  expect(result.current.count).toBe(0);
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});

已发布的钩子库

社区钩子库提供了数百个经过测试的钩子:@uidotdev/usehooks、react-use、usehooks-ts。在编写自定义钩子之前,请先检查是否已经存在经过充分测试的版本。

快速检查

当两个不同的组件调用同一个自定义钩子时,该钩子内部的状态会发生什么?

总结:自定义钩子

自定义钩子提取有状态逻辑以便复用。名称使用 use 前缀。每个调用该钩子的组件都会获得独立的状态。可以返回对象或元组。常见模式包括:useFetch、useLocalStorage、useDebounce、useMediaQuery。使用 renderHook() 进行测试。编写新钩子前,请先检查社区库中是否已有可用实现。

常见问题解答

「自定义 Hook:提取可复用逻辑」课时是免费的吗?

是的 — 「自定义 Hook:提取可复用逻辑」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Frontend Academy 课程的其余内容,请升级到 CoddyKit PRO。 Frontend Academy 课程共包含 4 节课。

「自定义 Hook:提取可复用逻辑」这节课中我会学到什么?

将有状态逻辑移入自定义的 use* 函数,在组件之间共享行为,避免重复代码或提升状态。 你通过在浏览器中直接运行的动手代码来练习 Frontend Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Frontend Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Frontend Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「自定义 Hook:提取可复用逻辑」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Frontend Academy 课中编写并运行代码吗?

能。每节 Frontend Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 useContext 管理全局状态
  2. 使用 useReducer 管理复杂状态
  3. 使用 useMemo 与 useCallback 提升性能
  4. 自定义 Hook:提取可复用逻辑
← 返回 Frontend Academy