React Hooks 深入解析:现代 React 开发的核心技术

React Hooks 是 React 16.8 引入的革命性特性,让函数组件拥有了状态管理和生命周期的能力。本文将深入解析 Hooks 的原理、用法和最佳实践。

2025年9月18日
DocsLib Team
ReactHooksJavaScript前端开发状态管理

React Hooks 深入解析:现代 React 开发的核心技术

React Hooks 简介

React Hooks 是 React 16.8 版本引入的新特性,它允许你在函数组件中使用状态和其他 React 特性。Hooks 的出现彻底改变了 React 开发的方式,使得函数组件能够完全替代类组件。

Hooks 的优势

  1. 简化组件逻辑:减少样板代码,提高代码可读性
  2. 更好的逻辑复用:通过自定义 Hooks 实现逻辑共享
  3. 更容易测试:函数组件比类组件更容易测试
  4. 更好的性能优化:避免不必要的重新渲染
  5. 更符合函数式编程:组件即函数的理念
  6. 更小的包体积:函数组件通常比类组件更轻量

基础 Hooks

useState:状态管理

useState 是最基础也是最常用的 Hook,用于在函数组件中添加状态。

import React, { useState } from 'react';

// 基础用法
function Counter() {
  const [count, setCount] = useState(0);

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

// 复杂状态管理
function UserProfile() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const updateUser = (field, value) => {
    setUser(prevUser => ({
      ...prevUser,
      [field]: value
    }));
  };

  return (
    <form>
      <input
        type="text"
        placeholder="姓名"
        value={user.name}
        onChange={(e) => updateUser('name', e.target.value)}
      />
      <input
        type="email"
        placeholder="邮箱"
        value={user.email}
        onChange={(e) => updateUser('email', e.target.value)}
      />
      <input
        type="number"
        placeholder="年龄"
        value={user.age}
        onChange={(e) => updateUser('age', parseInt(e.target.value))}
      />
      <div>
        <h3>用户信息预览:</h3>
        <p>姓名: {user.name}</p>
        <p>邮箱: {user.email}</p>
        <p>年龄: {user.age}</p>
      </div>
    </form>
  );
}

// 函数式更新
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    if (inputValue.trim()) {
      setTodos(prevTodos => [
        ...prevTodos,
        {
          id: Date.now(),
          text: inputValue,
          completed: false
        }
      ]);
      setInputValue('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    );
  };

  const deleteTodo = (id) => {
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <div>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="添加新任务"
        />
        <button onClick={addTodo}>添加</button>
      </div>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

useEffect:副作用处理

useEffect 用于处理副作用,如数据获取、订阅、手动 DOM 操作等。

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

// 基础用法
function DocumentTitle() {
  const [count, setCount] = useState(0);

  // 每次渲染后执行
  useEffect(() => {
    document.title = `计数: ${count}`;
  });

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加计数
      </button>
    </div>
  );
}

// 依赖数组控制执行时机
function UserData({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 只在 userId 变化时执行
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
          throw new Error('用户数据获取失败');
        }
        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (userId) {
      fetchUser();
    }
  }, [userId]); // 依赖数组

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;
  if (!user) return <div>未找到用户</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>邮箱: {user.email}</p>
      <p>注册时间: {user.createdAt}</p>
    </div>
  );
}

// 清理副作用
function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    let intervalId;

    if (isRunning) {
      intervalId = setInterval(() => {
        setSeconds(prevSeconds => prevSeconds + 1);
      }, 1000);
    }

    // 清理函数
    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
    };
  }, [isRunning]);

  const handleStart = () => setIsRunning(true);
  const handleStop = () => setIsRunning(false);
  const handleReset = () => {
    setIsRunning(false);
    setSeconds(0);
  };

  return (
    <div>
      <h2>计时器: {seconds} 秒</h2>
      <button onClick={handleStart} disabled={isRunning}>
        开始
      </button>
      <button onClick={handleStop} disabled={!isRunning}>
        停止
      </button>
      <button onClick={handleReset}>
        重置
      </button>
    </div>
  );
}

// 窗口大小监听
function WindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);

    // 清理事件监听器
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 空依赖数组,只在组件挂载和卸载时执行

  return (
    <div>
      <p>窗口宽度: {windowSize.width}px</p>
      <p>窗口高度: {windowSize.height}px</p>
    </div>
  );
}

useContext:上下文消费

useContext 用于消费 React Context,避免组件树中的 props 层层传递。

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

// 创建主题上下文
const ThemeContext = createContext();

// 主题提供者组件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  const themeValue = {
    theme,
    toggleTheme,
    colors: {
      light: {
        background: '#ffffff',
        text: '#000000',
        primary: '#007bff'
      },
      dark: {
        background: '#1a1a1a',
        text: '#ffffff',
        primary: '#4dabf7'
      }
    }
  };

  return (
    <ThemeContext.Provider value={themeValue}>
      {children}
    </ThemeContext.Provider>
  );
}

// 使用主题的组件
function ThemedButton({ children, onClick }) {
  const { theme, colors } = useContext(ThemeContext);
  const currentColors = colors[theme];

  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: currentColors.primary,
        color: currentColors.background,
        border: 'none',
        padding: '10px 20px',
        borderRadius: '5px',
        cursor: 'pointer'
      }}
    >
      {children}
    </button>
  );
}

// 主题切换器
function ThemeToggler() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <ThemedButton onClick={toggleTheme}>
      切换到 {theme === 'light' ? '深色' : '浅色'} 主题
    </ThemedButton>
  );
}

// 主应用组件
function ThemedApp() {
  const { theme, colors } = useContext(ThemeContext);
  const currentColors = colors[theme];

  return (
    <div
      style={{
        backgroundColor: currentColors.background,
        color: currentColors.text,
        minHeight: '100vh',
        padding: '20px'
      }}
    >
      <h1>主题切换示例</h1>
      <p>当前主题: {theme}</p>
      <ThemeToggler />
    </div>
  );
}

// 用户认证上下文示例
const AuthContext = createContext();

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

  // 模拟登录
  const login = async (email, password) => {
    setLoading(true);
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1000));
      const userData = {
        id: 1,
        email,
        name: '用户名',
        avatar: '/default-avatar.png'
      };
      setUser(userData);
      localStorage.setItem('user', JSON.stringify(userData));
    } catch (error) {
      throw new Error('登录失败');
    } finally {
      setLoading(false);
    }
  };

  // 登出
  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  // 检查本地存储的用户信息
  useEffect(() => {
    const savedUser = localStorage.getItem('user');
    if (savedUser) {
      setUser(JSON.parse(savedUser));
    }
    setLoading(false);
  }, []);

  const value = {
    user,
    login,
    logout,
    loading,
    isAuthenticated: !!user
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// 使用认证上下文的组件
function LoginForm() {
  const { login, loading } = useContext(AuthContext);
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      setError('');
      await login(email, password);
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="email"
          placeholder="邮箱"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>
      <div>
        <input
          type="password"
          placeholder="密码"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
      </div>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <button type="submit" disabled={loading}>
        {loading ? '登录中...' : '登录'}
      </button>
    </form>
  );
}

function UserProfile() {
  const { user, logout } = useContext(AuthContext);

  return (
    <div>
      <h2>欢迎, {user.name}!</h2>
      <p>邮箱: {user.email}</p>
      <button onClick={logout}>登出</button>
    </div>
  );
}

高级 Hooks

useReducer:复杂状态管理

useReducer 适用于复杂的状态逻辑,特别是当状态更新逻辑复杂或下一个状态依赖于之前的状态时。

import React, { useReducer } from 'react';

// 购物车状态管理
const cartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      const existingItem = state.items.find(item => item.id === action.payload.id);
      if (existingItem) {
        return {
          ...state,
          items: state.items.map(item =>
            item.id === action.payload.id
              ? { ...item, quantity: item.quantity + 1 }
              : item
          )
        };
      }
      return {
        ...state,
        items: [...state.items, { ...action.payload, quantity: 1 }]
      };

    case 'REMOVE_ITEM':
      return {
        ...state,
        items: state.items.filter(item => item.id !== action.payload)
      };

    case 'UPDATE_QUANTITY':
      return {
        ...state,
        items: state.items.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: Math.max(0, action.payload.quantity) }
            : item
        ).filter(item => item.quantity > 0)
      };

    case 'CLEAR_CART':
      return {
        ...state,
        items: []
      };

    case 'APPLY_DISCOUNT':
      return {
        ...state,
        discount: action.payload
      };

    default:
      return state;
  }
};

function ShoppingCart() {
  const [cart, dispatch] = useReducer(cartReducer, {
    items: [],
    discount: 0
  });

  // 计算总价
  const total = cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const discountedTotal = total * (1 - cart.discount);

  const addItem = (product) => {
    dispatch({ type: 'ADD_ITEM', payload: product });
  };

  const removeItem = (id) => {
    dispatch({ type: 'REMOVE_ITEM', payload: id });
  };

  const updateQuantity = (id, quantity) => {
    dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
  };

  const clearCart = () => {
    dispatch({ type: 'CLEAR_CART' });
  };

  const applyDiscount = (discount) => {
    dispatch({ type: 'APPLY_DISCOUNT', payload: discount });
  };

  // 示例商品
  const products = [
    { id: 1, name: 'iPhone 14', price: 999 },
    { id: 2, name: 'MacBook Pro', price: 1999 },
    { id: 3, name: 'AirPods Pro', price: 249 }
  ];

  return (
    <div>
      <h2>购物车</h2>
      
      {/* 商品列表 */}
      <div>
        <h3>商品</h3>
        {products.map(product => (
          <div key={product.id} style={{ margin: '10px 0' }}>
            <span>{product.name} - ${product.price}</span>
            <button onClick={() => addItem(product)}>添加到购物车</button>
          </div>
        ))}
      </div>

      {/* 购物车内容 */}
      <div>
        <h3>购物车内容</h3>
        {cart.items.length === 0 ? (
          <p>购物车为空</p>
        ) : (
          <>
            {cart.items.map(item => (
              <div key={item.id} style={{ margin: '10px 0' }}>
                <span>{item.name} - ${item.price}</span>
                <input
                  type="number"
                  value={item.quantity}
                  onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
                  min="0"
                  style={{ width: '60px', margin: '0 10px' }}
                />
                <span>小计: ${(item.price * item.quantity).toFixed(2)}</span>
                <button onClick={() => removeItem(item.id)}>删除</button>
              </div>
            ))}
            
            <div style={{ marginTop: '20px' }}>
              <p>原价: ${total.toFixed(2)}</p>
              {cart.discount > 0 && (
                <p>折扣: {(cart.discount * 100).toFixed(0)}%</p>
              )}
              <p><strong>总计: ${discountedTotal.toFixed(2)}</strong></p>
              
              <button onClick={() => applyDiscount(0.1)}>应用10%折扣</button>
              <button onClick={() => applyDiscount(0)}>移除折扣</button>
              <button onClick={clearCart}>清空购物车</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// 表单状态管理
const formReducer = (state, action) => {
  switch (action.type) {
    case 'SET_FIELD':
      return {
        ...state,
        values: {
          ...state.values,
          [action.field]: action.value
        },
        errors: {
          ...state.errors,
          [action.field]: ''
        }
      };

    case 'SET_ERROR':
      return {
        ...state,
        errors: {
          ...state.errors,
          [action.field]: action.error
        }
      };

    case 'SET_LOADING':
      return {
        ...state,
        loading: action.loading
      };

    case 'RESET_FORM':
      return action.initialState;

    default:
      return state;
  }
};

function ContactForm() {
  const initialState = {
    values: {
      name: '',
      email: '',
      message: ''
    },
    errors: {},
    loading: false
  };

  const [form, dispatch] = useReducer(formReducer, initialState);

  const setField = (field, value) => {
    dispatch({ type: 'SET_FIELD', field, value });
  };

  const setError = (field, error) => {
    dispatch({ type: 'SET_ERROR', field, error });
  };

  const validateForm = () => {
    let isValid = true;

    if (!form.values.name.trim()) {
      setError('name', '姓名不能为空');
      isValid = false;
    }

    if (!form.values.email.trim()) {
      setError('email', '邮箱不能为空');
      isValid = false;
    } else if (!/\S+@\S+\.\S+/.test(form.values.email)) {
      setError('email', '邮箱格式不正确');
      isValid = false;
    }

    if (!form.values.message.trim()) {
      setError('message', '消息不能为空');
      isValid = false;
    }

    return isValid;
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    if (!validateForm()) {
      return;
    }

    dispatch({ type: 'SET_LOADING', loading: true });

    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 2000));
      alert('表单提交成功!');
      dispatch({ type: 'RESET_FORM', initialState });
    } catch (error) {
      alert('提交失败,请重试');
    } finally {
      dispatch({ type: 'SET_LOADING', loading: false });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>姓名:</label>
        <input
          type="text"
          value={form.values.name}
          onChange={(e) => setField('name', e.target.value)}
        />
        {form.errors.name && <span style={{ color: 'red' }}>{form.errors.name}</span>}
      </div>

      <div>
        <label>邮箱:</label>
        <input
          type="email"
          value={form.values.email}
          onChange={(e) => setField('email', e.target.value)}
        />
        {form.errors.email && <span style={{ color: 'red' }}>{form.errors.email}</span>}
      </div>

      <div>
        <label>消息:</label>
        <textarea
          value={form.values.message}
          onChange={(e) => setField('message', e.target.value)}
          rows={4}
        />
        {form.errors.message && <span style={{ color: 'red' }}>{form.errors.message}</span>}
      </div>

      <button type="submit" disabled={form.loading}>
        {form.loading ? '提交中...' : '提交'}
      </button>
    </form>
  );
}

useMemo 和 useCallback:性能优化

这两个 Hook 用于优化性能,避免不必要的计算和重新渲染。

import React, { useState, useMemo, useCallback, memo } from 'react';

// useMemo 示例:昂贵的计算
function ExpensiveCalculation({ numbers }) {
  const [multiplier, setMultiplier] = useState(1);

  // 使用 useMemo 缓存昂贵的计算结果
  const expensiveValue = useMemo(() => {
    console.log('执行昂贵的计算...');
    return numbers.reduce((sum, num) => sum + num * multiplier, 0);
  }, [numbers, multiplier]); // 只有当 numbers 或 multiplier 变化时才重新计算

  // 不使用 useMemo 的对比
  const normalValue = numbers.reduce((sum, num) => sum + num * multiplier, 0);

  return (
    <div>
      <h3>性能优化示例</h3>
      <p>乘数: {multiplier}</p>
      <p>优化后的结果: {expensiveValue}</p>
      <p>普通计算结果: {normalValue}</p>
      <button onClick={() => setMultiplier(multiplier + 1)}>
        增加乘数
      </button>
    </div>
  );
}

// useCallback 示例:避免子组件不必要的重新渲染
const ChildComponent = memo(({ onButtonClick, value }) => {
  console.log('ChildComponent 重新渲染');
  
  return (
    <div>
      <p>子组件值: {value}</p>
      <button onClick={onButtonClick}>点击我</button>
    </div>
  );
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  // 使用 useCallback 缓存函数
  const handleButtonClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []); // 空依赖数组,函数永远不会改变

  // 不使用 useCallback 的对比
  const handleButtonClickNormal = () => {
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <h3>useCallback 示例</h3>
      <p>父组件计数: {count}</p>
      <p>其他状态: {otherState}</p>
      
      <button onClick={() => setOtherState(otherState + 1)}>
        改变其他状态
      </button>
      
      <ChildComponent 
        onButtonClick={handleButtonClick} 
        value={count} 
      />
    </div>
  );
}

// 复杂的 useMemo 和 useCallback 组合示例
function DataTable({ data, filters }) {
  const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
  const [selectedItems, setSelectedItems] = useState(new Set());

  // 使用 useMemo 缓存过滤和排序后的数据
  const processedData = useMemo(() => {
    console.log('处理数据...');
    
    // 过滤数据
    let filtered = data.filter(item => {
      return Object.entries(filters).every(([key, value]) => {
        if (!value) return true;
        return item[key].toString().toLowerCase().includes(value.toLowerCase());
      });
    });

    // 排序数据
    if (sortConfig.key) {
      filtered.sort((a, b) => {
        if (a[sortConfig.key] < b[sortConfig.key]) {
          return sortConfig.direction === 'asc' ? -1 : 1;
        }
        if (a[sortConfig.key] > b[sortConfig.key]) {
          return sortConfig.direction === 'asc' ? 1 : -1;
        }
        return 0;
      });
    }

    return filtered;
  }, [data, filters, sortConfig]);

  // 使用 useCallback 缓存事件处理函数
  const handleSort = useCallback((key) => {
    setSortConfig(prevConfig => ({
      key,
      direction: prevConfig.key === key && prevConfig.direction === 'asc' ? 'desc' : 'asc'
    }));
  }, []);

  const handleSelectItem = useCallback((id) => {
    setSelectedItems(prevSelected => {
      const newSelected = new Set(prevSelected);
      if (newSelected.has(id)) {
        newSelected.delete(id);
      } else {
        newSelected.add(id);
      }
      return newSelected;
    });
  }, []);

  const handleSelectAll = useCallback(() => {
    if (selectedItems.size === processedData.length) {
      setSelectedItems(new Set());
    } else {
      setSelectedItems(new Set(processedData.map(item => item.id)));
    }
  }, [processedData, selectedItems.size]);

  // 使用 useMemo 缓存统计信息
  const statistics = useMemo(() => {
    return {
      total: processedData.length,
      selected: selectedItems.size,
      averageAge: processedData.reduce((sum, item) => sum + item.age, 0) / processedData.length || 0
    };
  }, [processedData, selectedItems.size]);

  return (
    <div>
      <div>
        <h3>数据统计</h3>
        <p>总数: {statistics.total}</p>
        <p>已选择: {statistics.selected}</p>
        <p>平均年龄: {statistics.averageAge.toFixed(1)}</p>
      </div>

      <table>
        <thead>
          <tr>
            <th>
              <input
                type="checkbox"
                checked={selectedItems.size === processedData.length && processedData.length > 0}
                onChange={handleSelectAll}
              />
            </th>
            <th onClick={() => handleSort('name')} style={{ cursor: 'pointer' }}>
              姓名 {sortConfig.key === 'name' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
            </th>
            <th onClick={() => handleSort('age')} style={{ cursor: 'pointer' }}>
              年龄 {sortConfig.key === 'age' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
            </th>
            <th onClick={() => handleSort('email')} style={{ cursor: 'pointer' }}>
              邮箱 {sortConfig.key === 'email' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
            </th>
          </tr>
        </thead>
        <tbody>
          {processedData.map(item => (
            <tr key={item.id}>
              <td>
                <input
                  type="checkbox"
                  checked={selectedItems.has(item.id)}
                  onChange={() => handleSelectItem(item.id)}
                />
              </td>
              <td>{item.name}</td>
              <td>{item.age}</td>
              <td>{item.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

自定义 Hooks

自定义 Hooks 是 React Hooks 最强大的特性之一,允许你提取组件逻辑到可重用的函数中。

基础自定义 Hooks

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

// 本地存储 Hook
function useLocalStorage(key, initialValue) {
  // 获取初始值
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  // 设置值的函数
  const setValue = useCallback((value) => {
    try {
      // 允许值是一个函数,以便我们有与 useState 相同的 API
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  }, [key, storedValue]);

  return [storedValue, setValue];
}

// 使用本地存储 Hook 的组件
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
  const [notifications, setNotifications] = useLocalStorage('notifications', true);

  return (
    <div>
      <h3>设置</h3>
      
      <div>
        <label>主题:</label>
        <select value={theme} onChange={(e) => setTheme(e.target.value)}>
          <option value="light">浅色</option>
          <option value="dark">深色</option>
        </select>
      </div>

      <div>
        <label>语言:</label>
        <select value={language} onChange={(e) => setLanguage(e.target.value)}>
          <option value="zh-CN">中文</option>
          <option value="en-US">English</option>
        </select>
      </div>

      <div>
        <label>
          <input
            type="checkbox"
            checked={notifications}
            onChange={(e) => setNotifications(e.target.checked)}
          />
          启用通知
        </label>
      </div>

      <div>
        <h4>当前设置:</h4>
        <p>主题: {theme}</p>
        <p>语言: {language}</p>
        <p>通知: {notifications ? '开启' : '关闭'}</p>
      </div>
    </div>
  );
}

// 防抖 Hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

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

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

  return debouncedValue;
}

// 使用防抖 Hook 的搜索组件
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      setLoading(true);
      // 模拟 API 调用
      setTimeout(() => {
        const mockResults = [
          `结果 1 for "${debouncedSearchTerm}"`,
          `结果 2 for "${debouncedSearchTerm}"`,
          `结果 3 for "${debouncedSearchTerm}"`
        ];
        setResults(mockResults);
        setLoading(false);
      }, 1000);
    } else {
      setResults([]);
    }
  }, [debouncedSearchTerm]);

  return (
    <div>
      <input
        type="text"
        placeholder="搜索..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      
      {loading && <p>搜索中...</p>}
      
      <ul>
        {results.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
}

// 网络状态 Hook
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
}

// 使用网络状态 Hook 的组件
function NetworkStatus() {
  const isOnline = useOnlineStatus();

  return (
    <div style={{
      padding: '10px',
      backgroundColor: isOnline ? '#d4edda' : '#f8d7da',
      color: isOnline ? '#155724' : '#721c24',
      border: `1px solid ${isOnline ? '#c3e6cb' : '#f5c6cb'}`,
      borderRadius: '4px'
    }}>
      {isOnline ? '🟢 在线' : '🔴 离线'}
    </div>
  );
}

// 鼠标位置 Hook
function useMousePosition() {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (event) => {
      setMousePosition({ x: event.clientX, y: event.clientY });
    };

    window.addEventListener('mousemove', handleMouseMove);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, []);

  return mousePosition;
}

// 使用鼠标位置 Hook 的组件
function MouseTracker() {
  const { x, y } = useMousePosition();

  return (
    <div>
      <h3>鼠标追踪器</h3>
      <p>鼠标位置: ({x}, {y})</p>
      <div
        style={{
          position: 'fixed',
          top: y - 10,
          left: x - 10,
          width: '20px',
          height: '20px',
          backgroundColor: 'red',
          borderRadius: '50%',
          pointerEvents: 'none',
          zIndex: 9999
        }}
      />
    </div>
  );
}

高级自定义 Hooks

// 数据获取 Hook
function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const abortControllerRef = useRef(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);

      // 取消之前的请求
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }

      // 创建新的 AbortController
      abortControllerRef.current = new AbortController();

      const response = await fetch(url, {
        ...options,
        signal: abortControllerRef.current.signal
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const result = await response.json();
      setData(result);
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err.message);
      }
    } finally {
      setLoading(false);
    }
  }, [url, options]);

  useEffect(() => {
    fetchData();

    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
    };
  }, [fetchData]);

  const refetch = useCallback(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch };
}

// 使用数据获取 Hook 的组件
function UserList() {
  const { data: users, loading, error, refetch } = useFetch('/api/users');

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;

  return (
    <div>
      <h3>用户列表</h3>
      <button onClick={refetch}>刷新</button>
      <ul>
        {users?.map(user => (
          <li key={user.id}>{user.name} - {user.email}</li>
        ))}
      </ul>
    </div>
  );
}

// 表单验证 Hook
function useFormValidation(initialValues, validationRules) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});

  const setValue = useCallback((name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
    
    // 如果字段已经被触摸过,立即验证
    if (touched[name]) {
      validateField(name, value);
    }
  }, [touched]);

  const setTouched = useCallback((name) => {
    setTouched(prev => ({ ...prev, [name]: true }));
    validateField(name, values[name]);
  }, [values]);

  const validateField = useCallback((name, value) => {
    const rule = validationRules[name];
    if (!rule) return;

    let error = '';
    
    if (rule.required && (!value || value.toString().trim() === '')) {
      error = rule.required;
    } else if (rule.pattern && !rule.pattern.test(value)) {
      error = rule.patternMessage || '格式不正确';
    } else if (rule.minLength && value.length < rule.minLength) {
      error = `最少需要 ${rule.minLength} 个字符`;
    } else if (rule.maxLength && value.length > rule.maxLength) {
      error = `最多允许 ${rule.maxLength} 个字符`;
    } else if (rule.custom) {
      error = rule.custom(value, values);
    }

    setErrors(prev => ({ ...prev, [name]: error }));
    return error;
  }, [validationRules, values]);

  const validateAll = useCallback(() => {
    const newErrors = {};
    let isValid = true;

    Object.keys(validationRules).forEach(name => {
      const error = validateField(name, values[name]);
      if (error) {
        newErrors[name] = error;
        isValid = false;
      }
    });

    setErrors(newErrors);
    setTouched(Object.keys(validationRules).reduce((acc, key) => {
      acc[key] = true;
      return acc;
    }, {}));

    return isValid;
  }, [validationRules, values, validateField]);

  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
  }, [initialValues]);

  return {
    values,
    errors,
    touched,
    setValue,
    setTouched,
    validateAll,
    reset,
    isValid: Object.keys(errors).length === 0
  };
}

// 使用表单验证 Hook 的组件
function RegistrationForm() {
  const validationRules = {
    username: {
      required: '用户名不能为空',
      minLength: 3,
      maxLength: 20,
      pattern: /^[a-zA-Z0-9_]+$/,
      patternMessage: '用户名只能包含字母、数字和下划线'
    },
    email: {
      required: '邮箱不能为空',
      pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      patternMessage: '请输入有效的邮箱地址'
    },
    password: {
      required: '密码不能为空',
      minLength: 8,
      custom: (value) => {
        if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
          return '密码必须包含大小写字母和数字';
        }
        return '';
      }
    },
    confirmPassword: {
      required: '请确认密码',
      custom: (value, allValues) => {
        if (value !== allValues.password) {
          return '两次输入的密码不一致';
        }
        return '';
      }
    }
  };

  const {
    values,
    errors,
    touched,
    setValue,
    setTouched,
    validateAll,
    reset,
    isValid
  } = useFormValidation({
    username: '',
    email: '',
    password: '',
    confirmPassword: ''
  }, validationRules);

  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (validateAll()) {
      console.log('表单提交:', values);
      alert('注册成功!');
      reset();
    }
  };

  const handleInputChange = (name) => (e) => {
    setValue(name, e.target.value);
  };

  const handleInputBlur = (name) => () => {
    setTouched(name);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>用户名:</label>
        <input
          type="text"
          value={values.username}
          onChange={handleInputChange('username')}
          onBlur={handleInputBlur('username')}
        />
        {touched.username && errors.username && (
          <span style={{ color: 'red' }}>{errors.username}</span>
        )}
      </div>

      <div>
        <label>邮箱:</label>
        <input
          type="email"
          value={values.email}
          onChange={handleInputChange('email')}
          onBlur={handleInputBlur('email')}
        />
        {touched.email && errors.email && (
          <span style={{ color: 'red' }}>{errors.email}</span>
        )}
      </div>

      <div>
        <label>密码:</label>
        <input
          type="password"
          value={values.password}
          onChange={handleInputChange('password')}
          onBlur={handleInputBlur('password')}
        />
        {touched.password && errors.password && (
          <span style={{ color: 'red' }}>{errors.password}</span>
        )}
      </div>

      <div>
        <label>确认密码:</label>
        <input
          type="password"
          value={values.confirmPassword}
          onChange={handleInputChange('confirmPassword')}
          onBlur={handleInputBlur('confirmPassword')}
        />
        {touched.confirmPassword && errors.confirmPassword && (
          <span style={{ color: 'red' }}>{errors.confirmPassword}</span>
        )}
      </div>

      <div>
        <button type="submit" disabled={!isValid}>
          注册
        </button>
        <button type="button" onClick={reset}>
          重置
        </button>
      </div>
    </form>
  );
}

Hooks 最佳实践

1. Hooks 规则

// ✅ 正确:在函数组件顶层调用 Hooks
function MyComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <div>{count}</div>;
}

// ❌ 错误:在条件语句中调用 Hooks
function BadComponent({ shouldUseCount }) {
  if (shouldUseCount) {
    const [count, setCount] = useState(0); // 违反 Hooks 规则
  }
  
  return <div>Bad</div>;
}

// ✅ 正确:条件逻辑在 Hook 内部
function GoodComponent({ shouldUseCount }) {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    if (shouldUseCount) {
      // 条件逻辑在 Hook 内部
      document.title = `Count: ${count}`;
    }
  }, [count, shouldUseCount]);
  
  return <div>{shouldUseCount ? count : 'No count'}</div>;
}

2. 依赖数组最佳实践

// ✅ 正确:包含所有依赖
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // 包含 userId 依赖
  
  return <div>{user?.name}</div>;
}

// ❌ 错误:遗漏依赖
function BadUserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // 遗漏 userId 依赖
  
  return <div>{user?.name}</div>;
}

// ✅ 正确:使用 useCallback 稳定函数引用
function SearchComponent({ onSearch }) {
  const [query, setQuery] = useState('');
  
  const debouncedSearch = useCallback(
    debounce((searchQuery) => {
      onSearch(searchQuery);
    }, 300),
    [onSearch]
  );
  
  useEffect(() => {
    if (query) {
      debouncedSearch(query);
    }
  }, [query, debouncedSearch]);
  
  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

3. 性能优化技巧

// 使用 React.memo 优化子组件
const ExpensiveChild = memo(({ data, onUpdate }) => {
  console.log('ExpensiveChild 渲染');
  
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>
          {item.name}
          <button onClick={() => onUpdate(item.id)}>
            更新
          </button>
        </div>
      ))}
    </div>
  );
});

function OptimizedParent() {
  const [items, setItems] = useState([]);
  const [otherState, setOtherState] = useState(0);
  
  // 使用 useCallback 稳定函数引用
  const handleUpdate = useCallback((id) => {
    setItems(prevItems => 
      prevItems.map(item => 
        item.id === id 
          ? { ...item, updated: Date.now() }
          : item
      )
    );
  }, []);
  
  // 使用 useMemo 缓存计算结果
  const expensiveData = useMemo(() => {
    return items.filter(item => item.active)
                .sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);
  
  return (
    <div>
      <button onClick={() => setOtherState(otherState + 1)}>
        其他状态: {otherState}
      </button>
      
      <ExpensiveChild 
        data={expensiveData} 
        onUpdate={handleUpdate} 
      />
    </div>
  );
}

4. 错误处理

// 错误边界 Hook
function useErrorHandler() {
  const [error, setError] = useState(null);
  
  const resetError = useCallback(() => {
    setError(null);
  }, []);
  
  const captureError = useCallback((error) => {
    console.error('捕获到错误:', error);
    setError(error);
  }, []);
  
  return { error, resetError, captureError };
}

// 异步操作错误处理
function useAsyncOperation() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const execute = useCallback(async (asyncFunction) => {
    try {
      setLoading(true);
      setError(null);
      const result = await asyncFunction();
      return result;
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);
  
  return { loading, error, execute };
}

// 使用错误处理的组件
function DataComponent() {
  const [data, setData] = useState(null);
  const { loading, error, execute } = useAsyncOperation();
  
  const fetchData = useCallback(async () => {
    const result = await execute(async () => {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error('数据获取失败');
      }
      return response.json();
    });
    
    setData(result);
  }, [execute]);
  
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  
  return (
    <div>
      <h3>数据展示</h3>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button onClick={fetchData}>重新获取</button>
    </div>
  );
}

总结

React Hooks 是现代 React 开发的核心技术,它们提供了一种更简洁、更灵活的方式来管理组件状态和副作用。通过掌握基础 Hooks(useState、useEffect、useContext)和高级 Hooks(useReducer、useMemo、useCallback),以及学会创建自定义 Hooks,你可以构建更加高效和可维护的 React 应用。

关键要点

  1. 遵循 Hooks 规则:只在函数组件顶层调用 Hooks
  2. 正确使用依赖数组:确保包含所有依赖项
  3. 性能优化:合理使用 useMemo 和 useCallback
  4. 自定义 Hooks:提取可重用的逻辑
  5. 错误处理:构建健壮的应用

掌握这些概念和最佳实践,你就能充分发挥 React Hooks 的威力,构建出色的用户界面。

返回博客列表
感谢阅读!