React Hooks 实战指南

2026-06-22 · 6 阅读 · 423字
JavaScriptReact

React Hooks 实战指南

为什么需要 Hooks

在 Hooks 出现之前,React 组件分为类组件和函数组件。类组件有完整的生命周期方法,但存在几个问题:

  • 逻辑难以复用:高阶组件和 Render Props 会导致嵌套地狱
  • 复杂组件难以理解:生命周期中混入不相关的逻辑
  • this 指向困扰:需要手动绑定事件处理函数

Hooks 让函数组件也能使用状态和生命周期特性,逻辑复用变得简单。

useState

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
      <button onClick={() => setCount((prev) => prev - 1)}>减少</button>
    </div>
  );
}

注意事项:

  • 状态更新是异步的,多次调用会批量处理
  • 使用函数式更新可避免闭包陷阱:setCount(prev => prev + 1)
  • 对象状态需要手动合并(useState 不会自动合并)

useEffect

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);

    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data) => {
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      });

    return () => {
      cancelled = true; // 清理函数:避免竞态条件
    };
  }, [userId]); // 仅在 userId 变化时重新执行

  if (loading) return <div>加载中...</div>;
  return <div>{user?.name}</div>;
}

依赖数组规则:

  • 空数组 []:仅在挂载时执行,卸载时清理
  • 有依赖 [id]:依赖变化时重新执行
  • 不传参:每次渲染都执行(慎用)

useContext

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar />
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div className={`toolbar-${theme}`}>工具栏</div>;
}

useRef

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} />;
}

useRef 保存的值在组件整个生命周期内保持不变,且不会触发重新渲染。适合存储定时器 ID、DOM 引用等可变值。

useMemo 与 useCallback

import { useMemo, useCallback } from 'react';

function ExpenseList({ items, taxRate }) {
  const total = useMemo(() => {
    return items.reduce((sum, item) => sum + item.price * (1 + taxRate), 0);
  }, [items, taxRate]);

  const handleItemClick = useCallback((id) => {
    console.log('点击了项目:', id);
  }, [items]);

  return (
    <div>
      <p>总计: {total}</p>
      {items.map((item) => (
        <div key={item.id} onClick={() => handleItemClick(item.id)}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

自定义 Hooks

自定义 Hook 是 Hooks 逻辑复用的核心。

// useDebounce.ts
import { useState, useEffect } from 'react';

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

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

  return debouncedValue;
}

// 使用
function SearchPage() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      fetch(`/api/search?q=${debouncedQuery}`);
    }
  }, [debouncedQuery]);

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

Hooks 使用规则

  1. 只在顶层调用 Hooks:不要在条件、循环或嵌套函数中调用
  2. 只在 React 函数组件或自定义 Hook 中调用
  3. 自定义 Hook 必须以 use 开头

总结

Hooks 让 React 组件的逻辑复用和状态管理变得前所未有的简单。推荐从 useState、useEffect 开始,逐步学习 useReducer、useContext 等高级 Hook,通过自定义 Hook 提取业务逻辑,保持组件的简洁和可测试性。