避免卡壳!7个让 React 代码起飞的自定义 Hooks

更新日期: 2025-11-12 阅读: 12 标签: Hooks

在现代 react 开发中,自定义 Hooks(Custom Hooks) 是提升代码复用性、可维护性和可读性的核心工具。它们允许开发者将组件逻辑提取为可复用的函数,,彻底告别“渲染属性”(Render Props)和高阶组件(HOC)的繁琐模式。

本文精选 7 个在生产项目中实践过的高频自定义 Hooks,覆盖输入优化、数据持久化、异步请求、交互控制、状态追踪与响应式布局等核心场景。掌握它们,你将减少 60%+ 的重复代码,提升组件可测试性与可维护性。


useDebounce:防抖输入优化

useDebounce 接受值和延迟时间,返回防抖后的值;内部使用 useEffect 清理定时器,主要作用为延迟高频事件的执行,适用于搜索输入、窗口调整等场景,参考示实现:

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// 实战应用:搜索输入
function SearchInput() {
  const [input, setInput] = useState('');
  const debouncedInput = useDebounce(input, 500);

  useEffect(() => {
    if (debouncedInput) {
      // 触发搜索 api
      fetch(`/api/search?q=${debouncedInput}`);
    }
  }, [debouncedInput]);

  return <input value={input} onChange={e => setInput(e.target.value)} />;
}


useThrottle:节流控制频率

useThrottle 支持领先边/尾随边触发,基于 requestAnimationFrame 优化性能,主要作用为限制函数执行频率,适用于滚动监听、鼠标移动等连续事件,参考示实现:

import { useState, useEffect, useRef } from 'react';

function useThrottle<T>(value: T, limit: number): T {
  const [throttledValue, setThrottledValue] = useState<T>(value);
  const lastRun = useRef(Date.now());

  useEffect(() => {
    const handler = setTimeout(() => {
      if (Date.now() - lastRun.current >= limit) {
        setThrottledValue(value);
        lastRun.current = Date.now();
      }
    }, limit - (Date.now() - lastRun.current));

    return () => {
      clearTimeout(handler);
    };
  }, [value, limit]);

  return throttledValue;
}

// 实战应用:滚动加载
function InfiniteScroll() {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 200);

  useEffect(() => {
    const handleScroll = () => setScrollY(window.scrollY);
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  useEffect(() => {
    if (throttledScrollY > 1000) {
      // 加载更多
    }
  }, [throttledScrollY]);
}


useLocalStorage:持久化本地状态

useLocalStorage 自动序列化/反序列化,支持初始值和自定义存储键,主要作用为同步 React 状态与 localStorage,实现页面刷新后状态保留,参考示实现:

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// 实战应用:主题切换
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');

  useEffect(() => {
    document.documentElement.className = theme;
  }, [theme]);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      切换为 {theme === 'light' ? '暗黑' : '明亮'} 模式
    </button>
  );
}


useFetch:优雅的数据获取

useFetch 支持 AbortController 取消请求,兼容 Suspense,主要作用为封装 fetch 请求逻辑,提供 loading、error、data 状态,参考示实现:

import { useState, useEffect } from 'react';

interface FetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

function useFetch<T>(url: string, options?: RequestInit): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    setState(prev => ({ ...prev, loading: true }));

    fetch(url, { ...options, signal })
      .then(res => res.json())
      .then(data => {
        setState({ data, loading: false, error: null });
      })
      .catch(error => {
        if (error.name !== 'AbortError') {
          setState({ data: null, loading: false, error });
        }
      });

    return () => controller.abort();
  }, [url]);

  return state;
}

// 实战应用
function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error } = useFetch<User>(`/api/users/${userId}`);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  return <div>欢迎, {data?.name}</div>;
}


usePrevious:追踪前一个状态

usePrevious 使用 useRef 存储历史值,渲染间不触发重新渲染,主要作用为获取上一次渲染时的状态或 props 值,用于比较变化,参考示实现:

import { useRef, useEffect } from 'react';

function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// 实战应用:检测值变化
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>当前: {count}, 上次: {prevCount ?? '无'}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}


useClickOutside:点击外部关闭

useClickOutside 基于事件捕获,自动绑定/解绑事件,主要作用为检测点击是否发生在指定元素外部,常用于下拉菜单、模态框。参考如下:

import { useEffect, RefObject } from 'react';

function useClickOutside<T extends htmlElement>(
  ref: RefObject<T>,
  handler: (event: MouseEvent) => void
) {
  useEffect(() => {
    const listener = (event: MouseEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) {
        return;
      }
      handler(event);
    };

    document.addEventListener('mousedown', listener);
    return () => {
      document.removeEventListener('mousedown', listener);
    };
  }, [ref, handler]);
}

// 实战应用:下拉菜单
function Dropdown() {
  const [open, setOpen] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);

  useClickOutside(dropdownRef, () => setOpen(false));

  return (
    <div ref={dropdownRef} style={{ position: 'relative' }}>
      <button onClick={() => setOpen(!open)}>菜单</button>
      {open && (
        <ul style={{ position: 'absolute', background: 'white', border: '1px solid #ccc' }}>
          <li>选项1</li>
          <li>选项2</li>
        </ul>
      )}
    </div>
  );
}


useMediaQuery:响应式媒体查询

useMediaQuery 使用 window.matchMedia,支持 SSR 安全,主要作用为 监听 css 媒体查询状态,返回布尔值以驱动响应式逻辑。参考实现:

import { useState, useEffect } from 'react';

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(() => {
    if (typeof window === 'undefined') return false;
    return window.matchMedia(query).matches;
  });

  useEffect(() => {
    const media = window.matchMedia(query);
    const listener = (e: MediaQueryListEvent) => setMatches(e.matches);

    media.addEventListener('change', listener);
    return () => media.removeEventListener('change', listener);
  }, [query]);

  return matches;
}

// 实战应用:响应式布局
function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return <div>{isMobile ? '移动端视图' : '桌面端视图'}</div>;
}

这7个 Custom Hooks 涵盖了 React 开发中最常见的逻辑抽象:输入优化(debounce/throttle)、状态持久化(localStorage)、数据获取(fetch)、历史追踪(previous)、交互控制(clickOutside)、响应式适配(mediaQuery)。建议大家在日常开发中将常见的 Hooks 封装进项目内的 hooks/ 目录,形成共享的工具库。

来源公众号:FED实验室

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13166

React将引入Hooks,你怎么看?

近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

使用react hooks实现自己的context-redux

我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理

useEffect Hook 是如何工作的?

使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。

React Hooks 你真的用对了吗?

从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考

如何用 Hooks 来实现 React Class Component 写法?

Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的

React-Hooks

以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。

React Hooks与setInterval

Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?

React Hooks 底层解析[译]

对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例

React Hooks实践

9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!