React Hooks完全指南

1. Hooks概述

什么是Hooks?

Hooks是React 16.8引入的新特性,让你能在函数组件中使用状态和其他React特性,而不需要编写类组件。

Hooks的优势

  • 更简洁的代码:减少样板代码
  • 更好的逻辑复用:通过自定义Hook实现
  • 更容易测试:纯函数更易测试
  • 更好的性能:避免类组件的开销

2. 基础Hooks

2.1 useState - 状态管理

基本用法
jsx 复制代码
import { useState } from 'react';

function Counter() {
  // 声明状态变量
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      
      <input 
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="输入姓名"
      />
    </div>
  );
}
复杂状态管理
jsx 复制代码
function UserForm() {
  // 对象状态
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
  
  // 数组状态
  const [hobbies, setHobbies] = useState([]);
  
  // 更新对象状态
  const updateUser = (field, value) => {
    setUser(prevUser => ({
      ...prevUser,
      [field]: value
    }));
  };
  
  // 添加爱好
  const addHobby = (hobby) => {
    setHobbies(prevHobbies => [...prevHobbies, hobby]);
  };
  
  // 删除爱好
  const removeHobby = (index) => {
    setHobbies(prevHobbies => prevHobbies.filter((_, i) => i !== index));
  };
  
  return (
    <form>
      <input 
        value={user.name}
        onChange={e => updateUser('name', e.target.value)}
        placeholder="姓名"
      />
      <input 
        value={user.email}
        onChange={e => updateUser('email', e.target.value)}
        placeholder="邮箱"
      />
      
      <div>
        <h3>爱好列表:</h3>
        {hobbies.map((hobby, index) => (
          <div key={index}>
            <span>{hobby}</span>
            <button onClick={() => removeHobby(index)}>删除</button>
          </div>
        ))}
      </div>
    </form>
  );
}
useState简化实现
javascript 复制代码
// 简化的useState实现
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
    renderWithHooks(); // 重新渲染
  };
}

function useState(initVal) {
  if (firstRun) {
    state.push(initVal);
    setters.push(createSetter(cursor));
    firstRun = false;
  }
  
  const setter = setters[cursor];
  const value = state[cursor];
  
  cursor++;
  return [value, setter];
}

function renderWithHooks() {
  cursor = 0;
  // 重新渲染组件逻辑
}

2.2 useEffect - 副作用处理

基本用法
jsx 复制代码
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // 组件挂载和userId变化时执行
  useEffect(() => {
    console.log('Effect运行');
    
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        setUser(userData);
      } catch (error) {
        console.error('获取用户失败:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]); // 依赖数组
  
  // 只在组件挂载时执行一次
  useEffect(() => {
    document.title = `用户资料 - ${user?.name || '加载中'}`;
  }, [user?.name]);
  
  // 清理副作用
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行');
    }, 1000);
    
    // 返回清理函数
    return () => {
      clearInterval(timer);
      console.log('定时器清理');
    };
  }, []);
  
  if (loading) return <div>加载中...</div>;
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}
高级useEffect模式
jsx 复制代码
function AdvancedEffects() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);
  const [posts, setPosts] = useState([]);
  
  // 防抖Effect
  useEffect(() => {
    const timeoutId = setTimeout(() => {
      console.log('防抖执行');
    }, 500);
    
    return () => clearTimeout(timeoutId);
  }, [windowWidth]);
  
  // 窗口大小监听
  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };
    
    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  // 异步数据获取与取消
  useEffect(() => {
    let cancelled = false;
    
    const fetchPosts = async () => {
      try {
        const response = await fetch('/api/posts');
        const data = await response.json();
        
        if (!cancelled) {
          setPosts(data);
        }
      } catch (error) {
        if (!cancelled) {
          console.error('获取文章失败:', error);
        }
      }
    };
    
    fetchPosts();
    
    return () => {
      cancelled = true;
    };
  }, []);
  
  return (
    <div>
      <p>窗口宽度: {windowWidth}</p>
      <p>文章数量: {posts.length}</p>
    </div>
  );
}
useEffect简化实现
javascript 复制代码
// 简化的useEffect实现
let effects = [];
let effectCursor = 0;

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray;
  const deps = effects[effectCursor] ? effects[effectCursor].deps : undefined;
  const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true;
  
  if (hasNoDeps || hasChangedDeps) {
    // 执行清理函数
    if (effects[effectCursor] && effects[effectCursor].cleanup) {
      effects[effectCursor].cleanup();
    }
    
    // 执行副作用
    const cleanup = callback();
    
    effects[effectCursor] = {
      deps: depArray,
      cleanup
    };
  }
  
  effectCursor++;
}

3. 高级Hooks

3.1 useReducer - 复杂状态管理

基本用法
jsx 复制代码
import { useReducer } from 'react';

// 定义reducer函数
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    case 'set':
      return { count: action.payload };
    default:
      throw new Error(`未知的action类型: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
      <button onClick={() => dispatch({ type: 'set', payload: 10 })}>设为10</button>
    </div>
  );
}
复杂状态管理示例
jsx 复制代码
// 购物车reducer
function 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
          )
        };
      } else {
        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: action.payload.quantity }
            : item
        )
      };
      
    case 'CLEAR_CART':
      return {
        ...state,
        items: []
      };
      
    default:
      return state;
  }
}

function ShoppingCart() {
  const [cart, dispatch] = useReducer(cartReducer, { items: [] });
  
  const addItem = (product) => {
    dispatch({ type: 'ADD_ITEM', payload: product });
  };
  
  const removeItem = (productId) => {
    dispatch({ type: 'REMOVE_ITEM', payload: productId });
  };
  
  const updateQuantity = (productId, quantity) => {
    dispatch({ 
      type: 'UPDATE_QUANTITY', 
      payload: { id: productId, quantity } 
    });
  };
  
  const totalPrice = cart.items.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0
  );
  
  return (
    <div>
      <h2>购物车</h2>
      {cart.items.map(item => (
        <div key={item.id}>
          <span>{item.name} - ¥{item.price}</span>
          <input
            type="number"
            value={item.quantity}
            onChange={e => updateQuantity(item.id, parseInt(e.target.value))}
            min="1"
          />
          <button onClick={() => removeItem(item.id)}>删除</button>
        </div>
      ))}
      <p>总价: ¥{totalPrice}</p>
      <button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
        清空购物车
      </button>
    </div>
  );
}

3.2 useContext - 跨组件状态共享

基本用法
jsx 复制代码
import { createContext, useContext, useReducer } from 'react';

// 创建Context
const AuthContext = createContext();

// Auth reducer
function authReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user,
        token: action.payload.token
      };
    case 'LOGOUT':
      return {
        ...state,
        isAuthenticated: false,
        user: null,
        token: null
      };
    case 'UPDATE_USER':
      return {
        ...state,
        user: { ...state.user, ...action.payload }
      };
    default:
      return state;
  }
}

// AuthProvider组件
function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, {
    isAuthenticated: false,
    user: null,
    token: null,
    loading: false
  });
  
  const login = async (email, password) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });
      
      const data = await response.json();
      
      dispatch({
        type: 'LOGIN',
        payload: { user: data.user, token: data.token }
      });
      
      localStorage.setItem('token', data.token);
    } catch (error) {
      console.error('登录失败:', error);
    }
  };
  
  const logout = () => {
    dispatch({ type: 'LOGOUT' });
    localStorage.removeItem('token');
  };
  
  const updateUser = (userData) => {
    dispatch({ type: 'UPDATE_USER', payload: userData });
  };
  
  return (
    <AuthContext.Provider value={{
      ...state,
      login,
      logout,
      updateUser
    }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义Hook
function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth必须在AuthProvider内部使用');
  }
  return context;
}

// 使用Context的组件
function LoginForm() {
  const { login, isAuthenticated } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    login(email, password);
  };
  
  if (isAuthenticated) {
    return <div>已登录</div>;
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="邮箱"
      />
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
        placeholder="密码"
      />
      <button type="submit">登录</button>
    </form>
  );
}

function UserProfile() {
  const { user, updateUser, logout } = useAuth();
  
  return (
    <div>
      <h2>用户资料</h2>
      <p>姓名: {user?.name}</p>
      <p>邮箱: {user?.email}</p>
      <button onClick={() => updateUser({ name: '新姓名' })}>
        更新姓名
      </button>
      <button onClick={logout}>退出登录</button>
    </div>
  );
}

3.3 useMemo - 性能优化

基本用法
jsx 复制代码
import { useState, useMemo } from 'react';

function ExpensiveComponent({ items, filter }) {
  const [count, setCount] = useState(0);
  
  // 缓存昂贵的计算
  const filteredItems = useMemo(() => {
    console.log('执行过滤计算');
    return items.filter(item => 
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);
  
  // 缓存派生数据
  const stats = useMemo(() => {
    console.log('计算统计数据');
    return {
      total: filteredItems.length,
      completed: filteredItems.filter(item => item.completed).length,
      pending: filteredItems.filter(item => !item.completed).length
    };
  }, [filteredItems]);
  
  // 复杂计算示例
  const expensiveValue = useMemo(() => {
    console.log('执行复杂计算');
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.random();
    }
    return result;
  }, [items.length]); // 只有当items数量变化时才重新计算
  
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>增加计数</button>
      
      <div>
        <h3>统计信息</h3>
        <p>总计: {stats.total}</p>
        <p>已完成: {stats.completed}</p>
        <p>待处理: {stats.pending}</p>
        <p>复杂计算结果: {expensiveValue.toFixed(2)}</p>
      </div>
      
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

3.4 useCallback - 函数缓存

基本用法
jsx 复制代码
import { useState, useCallback, memo } from 'react';

// 子组件使用memo包装
const ChildComponent = memo(function ChildComponent({ onClick, data }) {
  console.log('ChildComponent渲染');
  
  return (
    <div>
      <p>{data.name}</p>
      <button onClick={onClick}>点击</button>
    </div>
  );
});

function ParentComponent({ items }) {
  const [count, setCount] = useState(0);
  const [selectedId, setSelectedId] = useState(null);
  
  // 缓存回调函数
  const handleItemClick = useCallback((itemId) => {
    console.log('点击了项目:', itemId);
    setSelectedId(itemId);
  }, []);
  
  // 带参数的回调缓存
  const handleItemUpdate = useCallback((itemId, newData) => {
    console.log('更新项目:', itemId, newData);
    // 更新逻辑
  }, []);
  
  // 复杂的回调逻辑
  const processItem = useCallback((item) => {
    // 只有当selectedId变化时才重新创建函数
    return {
      ...item,
      isSelected: item.id === selectedId,
      onClick: () => handleItemClick(item.id)
    };
  }, [selectedId, handleItemClick]);
  
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>
        增加计数
      </button>
      
      {items.map(item => {
        const processedItem = processItem(item);
        return (
          <ChildComponent
            key={item.id}
            data={processedItem}
            onClick={processedItem.onClick}
          />
        );
      })}
    </div>
  );
}
useCallback与事件处理
jsx 复制代码
function TodoList({ todos, onToggle, onDelete, onEdit }) {
  const [editingId, setEditingId] = useState(null);
  const [editText, setEditText] = useState('');
  
  // 缓存编辑相关函数
  const startEdit = useCallback((todo) => {
    setEditingId(todo.id);
    setEditText(todo.text);
  }, []);
  
  const saveEdit = useCallback(() => {
    if (editingId && editText.trim()) {
      onEdit(editingId, editText);
      setEditingId(null);
      setEditText('');
    }
  }, [editingId, editText, onEdit]);
  
  const cancelEdit = useCallback(() => {
    setEditingId(null);
    setEditText('');
  }, []);
  
  // 缓存Toggle函数
  const handleToggle = useCallback((todoId) => {
    onToggle(todoId);
  }, [onToggle]);
  
  // 缓存删除函数
  const handleDelete = useCallback((todoId) => {
    if (window.confirm('确定删除吗?')) {
      onDelete(todoId);
    }
  }, [onDelete]);
  
  return (
    <div>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          isEditing={editingId === todo.id}
          editText={editText}
          onToggle={() => handleToggle(todo.id)}
          onDelete={() => handleDelete(todo.id)}
          onStartEdit={() => startEdit(todo)}
          onSaveEdit={saveEdit}
          onCancelEdit={cancelEdit}
          onEditTextChange={setEditText}
        />
      ))}
    </div>
  );
}

const TodoItem = memo(function TodoItem({
  todo,
  isEditing,
  editText,
  onToggle,
  onDelete,
  onStartEdit,
  onSaveEdit,
  onCancelEdit,
  onEditTextChange
}) {
  if (isEditing) {
    return (
      <div>
        <input
          value={editText}
          onChange={e => onEditTextChange(e.target.value)}
          onKeyPress={e => e.key === 'Enter' && onSaveEdit()}
        />
        <button onClick={onSaveEdit}>保存</button>
        <button onClick={onCancelEdit}>取消</button>
      </div>
    );
  }
  
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={onToggle}
      />
      <span 
        style={{ 
          textDecoration: todo.completed ? 'line-through' : 'none' 
        }}
      >
        {todo.text}
      </span>
      <button onClick={onStartEdit}>编辑</button>
      <button onClick={onDelete}>删除</button>
    </div>
  );
});

4. 其他有用的Hooks

4.1 useRef - 引用DOM和保存变量

jsx 复制代码
import { useRef, useEffect, useState } from 'react';

function RefExamples() {
  const inputRef = useRef(null);
  const timerRef = useRef(null);
  const countRef = useRef(0);
  const [renderCount, setRenderCount] = useState(0);
  
  // 聚焦输入框
  const focusInput = () => {
    inputRef.current?.focus();
  };
  
  // 启动定时器
  const startTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
    }
    
    timerRef.current = setInterval(() => {
      countRef.current += 1;
      console.log('定时器计数:', countRef.current);
    }, 1000);
  };
  
  // 停止定时器
  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  };
  
  // 获取前一次的值
  function usePrevious(value) {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
  }
  
  const prevRenderCount = usePrevious(renderCount);
  
  useEffect(() => {
    return () => {
      // 组件卸载时清理定时器
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  }, []);
  
  return (
    <div>
      <input ref={inputRef} placeholder="点击按钮聚焦" />
      <button onClick={focusInput}>聚焦输入框</button>
      
      <div>
        <p>渲染次数: {renderCount}</p>
        <p>上次渲染次数: {prevRenderCount}</p>
        <button onClick={() => setRenderCount(c => c + 1)}>
          触发重新渲染
        </button>
      </div>
      
      <div>
        <button onClick={startTimer}>启动定时器</button>
        <button onClick={stopTimer}>停止定时器</button>
        <p>定时器计数: {countRef.current}</p>
      </div>
    </div>
  );
}

4.2 useLayoutEffect - 同步副作用

jsx 复制代码
import { useLayoutEffect, useEffect, useRef, useState } from 'react';

function LayoutEffectExample() {
  const [width, setWidth] = useState(0);
  const divRef = useRef(null);
  
  // useLayoutEffect在DOM更新后同步执行
  useLayoutEffect(() => {
    if (divRef.current) {
      const rect = divRef.current.getBoundingClientRect();
      setWidth(rect.width);
    }
  });
  
  // useEffect异步执行,可能造成闪烁
  useEffect(() => {
    console.log('useEffect执行');
  });
  
  return (
    <div>
      <div ref={divRef} style={{ width: '50%', background: 'lightblue' }}>
        这个div的宽度是: {width}px
      </div>
    </div>
  );
}

5. 自定义Hooks

5.1 数据获取Hook

jsx 复制代码
import { useState, useEffect, useRef } from 'react';

function useApi(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const cancelRef = useRef();
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        // 取消之前的请求
        if (cancelRef.current) {
          cancelRef.current.abort();
        }
        
        // 创建新的AbortController
        const controller = new AbortController();
        cancelRef.current = controller;
        
        const response = await fetch(url, {
          ...options,
          signal: controller.signal
        });
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const result = await response.json();
        
        if (!cancelled) {
          setData(result);
        }
      } catch (err) {
        if (!cancelled && err.name !== 'AbortError') {
          setError(err);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
      if (cancelRef.current) {
        cancelRef.current.abort();
      }
    };
  }, [url, JSON.stringify(options)]);
  
  const refetch = () => {
    setLoading(true);
    setError(null);
    // 触发重新获取
  };
  
  return { data, loading, error, refetch };
}

// 使用示例
function UserList() {
  const { data: users, loading, error, refetch } = useApi('/api/users');
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  
  return (
    <div>
      <button onClick={refetch}>刷新</button>
      {users?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

5.2 本地存储Hook

jsx 复制代码
import { useState, useEffect } from 'react';

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 = (value) => {
    try {
      // 允许传入函数
      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);
    }
  };
  
  // 监听localStorage变化
  useEffect(() => {
    const handleStorageChange = (e) => {
      if (e.key === key && e.newValue !== null) {
        try {
          setStoredValue(JSON.parse(e.newValue));
        } catch (error) {
          console.error('Error parsing localStorage value:', error);
        }
      }
    };
    
    window.addEventListener('storage', handleStorageChange);
    
    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  }, [key]);
  
  return [storedValue, setValue];
}

// 使用示例
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
  
  return (
    <div>
      <h3>设置</h3>
      <label>
        主题:
        <select value={theme} onChange={e => setTheme(e.target.value)}>
          <option value="light">亮色</option>
          <option value="dark">暗色</option>
        </select>
      </label>
      
      <label>
        语言:
        <select value={language} onChange={e => setLanguage(e.target.value)}>
          <option value="zh-CN">中文</option>
          <option value="en-US">English</option>
        </select>
      </label>
    </div>
  );
}

5.3 防抖和节流Hook

jsx 复制代码
import { useState, useEffect, useRef, useCallback } from 'react';

// 防抖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 useDebounceCallback(callback, delay, deps) {
  const timeoutRef = useRef();
  
  const debouncedCallback = useCallback((...args) => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  }, [callback, delay, ...deps]);
  
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
  
  return debouncedCallback;
}

// 节流Hook
function useThrottle(value, delay) {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastExecuted = useRef(Date.now());
  
  useEffect(() => {
    if (Date.now() >= lastExecuted.current + delay) {
      lastExecuted.current = Date.now();
      setThrottledValue(value);
    } else {
      const timerId = setTimeout(() => {
        lastExecuted.current = Date.now();
        setThrottledValue(value);
      }, delay);
      
      return () => clearTimeout(timerId);
    }
  }, [value, delay]);
  
  return throttledValue;
}

// 使用示例
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  
  // 防抖搜索词
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  
  // 防抖搜索函数
  const debouncedSearch = useDebounceCallback(async (term) => {
    if (term) {
      try {
        const response = await fetch(`/api/search?q=${encodeURIComponent(term)}`);
        const data = await response.json();
        setResults(data);
      } catch (error) {
        console.error('搜索失败:', error);
      }
    } else {
      setResults([]);
    }
  }, 300, []);
  
  // 监听防抖后的搜索词
  useEffect(() => {
    debouncedSearch(debouncedSearchTerm);
  }, [debouncedSearchTerm, debouncedSearch]);
  
  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
        placeholder="输入搜索关键词"
      />
      
      <div>
        {results.map(result => (
          <div key={result.id}>{result.title}</div>
        ))}
      </div>
    </div>
  );
}

6. Hooks规则和注意事项

6.1 Hooks规则

jsx 复制代码
// ✅ 正确:在函数组件顶层调用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() {
  const [items, setItems] = useState([]);
  
  for (let i = 0; i < items.length; i++) {
    const [itemState, setItemState] = useState(null); // 错误!
  }
  
  return <div></div>;
}

// ❌ 错误:在条件语句中调用Hooks
function AnotherBadComponent({ shouldShowName }) {
  const [count, setCount] = useState(0);
  
  if (shouldShowName) {
    const [name, setName] = useState(''); // 错误!
  }
  
  return <div>{count}</div>;
}

// ✅ 正确:条件逻辑在Hook内部
function GoodComponent({ shouldShowName }) {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  return (
    <div>
      {count}
      {shouldShowName && <span>{name}</span>}
    </div>
  );
}

6.2 常见陷阱和解决方案

jsx 复制代码
// 陷阱1: 在useEffect中缺少依赖
function BadEffect({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // 缺少userId依赖
  
  return <div>{user?.name}</div>;
}

// 解决方案
function GoodEffect({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // 包含userId依赖
  
  return <div>{user?.name}</div>;
}

// 陷阱2: 状态更新依赖当前状态
function BadCounter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(count + 1); // 可能出现竞态条件
  };
  
  return <button onClick={increment}>{count}</button>;
}

// 解决方案
function GoodCounter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(prevCount => prevCount + 1); // 使用函数式更新
  };
  
  return <button onClick={increment}>{count}</button>;
}

// 陷阱3: useEffect清理不当
function BadTimer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    // 忘记清理定时器
  }, []);
  
  return <div>{seconds}</div>;
}

// 解决方案
function GoodTimer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    return () => clearInterval(interval); // 正确清理
  }, []);
  
  return <div>{seconds}</div>;
}

7. Hooks原理简化实现

javascript 复制代码
// React Hooks的简化实现
let hookIndex = 0;
let hooks = [];

// 模拟React的调度系统
function scheduleRender() {
  hookIndex = 0;
  renderComponent();
}

// useState实现
function useState(initialValue) {
  const currentIndex = hookIndex;
  
  if (hooks[currentIndex] === undefined) {
    hooks[currentIndex] = initialValue;
  }
  
  const setState = (newValue) => {
    if (typeof newValue === 'function') {
      hooks[currentIndex] = newValue(hooks[currentIndex]);
    } else {
      hooks[currentIndex] = newValue;
    }
    scheduleRender();
  };
  
  hookIndex++;
  return [hooks[currentIndex], setState];
}

// useEffect实现
function useEffect(callback, dependencies) {
  const currentIndex = hookIndex;
  const hasChanged = hasDepArrayChanged(
    hooks[currentIndex] ? hooks[currentIndex].dependencies : undefined,
    dependencies
  );
  
  if (!hooks[currentIndex] || hasChanged) {
    // 执行清理函数
    if (hooks[currentIndex] && hooks[currentIndex].cleanup) {
      hooks[currentIndex].cleanup();
    }
    
    // 执行effect
    const cleanup = callback();
    
    hooks[currentIndex] = {
      dependencies,
      cleanup
    };
  }
  
  hookIndex++;
}

// 检查依赖数组是否变化
function hasDepArrayChanged(prevDeps, deps) {
  if (prevDeps === null) return true;
  if (deps === null) return true;
  if (prevDeps.length !== deps.length) return true;
  
  for (let i = 0; i < prevDeps.length; i++) {
    if (prevDeps[i] !== deps[i]) {
      return true;
    }
  }
  
  return false;
}

// useReducer实现
function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);
  
  const dispatch = (action) => {
    const newState = reducer(state, action);
    setState(newState);
  };
  
  return [state, dispatch];
}

// useMemo实现
function useMemo(factory, dependencies) {
  const currentIndex = hookIndex;
  const hasChanged = hasDepArrayChanged(
    hooks[currentIndex] ? hooks[currentIndex].dependencies : undefined,
    dependencies
  );
  
  if (!hooks[currentIndex] || hasChanged) {
    const value = factory();
    hooks[currentIndex] = {
      value,
      dependencies
    };
  }
  
  hookIndex++;
  return hooks[currentIndex].value;
}

// useCallback实现
function useCallback(callback, dependencies) {
  return useMemo(() => callback, dependencies);
}

8. 测试Hooks

jsx 复制代码
import { renderHook, act } from '@testing-library/react-hooks';
import { useState } from 'react';

// 测试自定义Hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);
  
  return { count, increment, decrement, reset };
}

// 测试用例
describe('useCounter', () => {
  test('初始值应该正确', () => {
    const { result } = renderHook(() => useCounter(10));
    
    expect(result.current.count).toBe(10);
  });
  
  test('increment应该增加计数', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  test('reset应该重置到初始值', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.increment();
      result.current.increment();
    });
    
    expect(result.current.count).toBe(7);
    
    act(() => {
      result.current.reset();
    });
    
    expect(result.current.count).toBe(5);
  });
});

React Hooks是现代React开发的核心特性,它们提供了一种更简洁、更强大的方式来管理组件状态和副作用。掌握这些Hooks的用法和原理,将大大提升你的React开发能力。

记住Hooks的两个基本规则:

  1. 只能在函数组件的顶层调用Hooks
  2. 只能在React函数组件和自定义Hook中调用Hooks

通过合理使用内置Hooks和创建自定义Hooks,你可以构建出更加优雅和可维护的React应用。

相关推荐
Moment4 小时前
性能狂飙!Next.js 16 重磅发布:Turbopack 稳定、编译提速 10 倍!🚀🚀🚀
前端·javascript·后端
软件技术NINI4 小时前
html css js网页制作成品——HTML+CSS仙台有树电视剧网页设计(5页)附源码
javascript·css·html
DoraBigHead5 小时前
React Fiber:从“递归地狱”到“时间切片”的重生之路
前端·javascript·react.js
YUELEI1185 小时前
Vue 安装依赖的集合和小知识
javascript·vue.js·ecmascript
lecepin6 小时前
AI Coding 资讯 2025-10-22
前端·javascript·后端
gustt6 小时前
深入理解 JavaScript 的对象与代理模式(Proxy)
javascript
3秒一个大6 小时前
JavaScript 对象:从字面量到代理模式的灵活世界
javascript
BumBle6 小时前
uniapp AI聊天应用技术解析:实现流畅的Streaming聊天体验(基础版本)
前端·uni-app
搞个锤子哟6 小时前
vant4的van-pull-refresh里的列表不在顶部时下拉也会触发刷新的问题
前端