教程5 min read

React Hooks 完全指南

什么是 React Hooks?

React Hooks 是 React 16.8 引入的新特性,让你在函数组件中使用状态和其他 React 特性。

Hooks 的出现改变了我们编写 React 组件的方式,使得函数组件变得更加强大和灵活。

为什么需要 Hooks?

  1. 更简洁的代码 - 函数组件比类组件更简洁
  2. 更好的逻辑复用 - 自定义 Hooks 可以轻松复用状态逻辑
  3. 更直观的数据流 - 相关的逻辑可以组织在一起
  4. 更容易测试 - 纯函数更容易测试

核心 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(count - 1)}>
        减少
      </button>
      <button onClick={() => setCount(0)}>
        重置
      </button>
    </div>
  );
}

useEffect

处理副作用操作:

import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchUser() {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
          throw new Error('获取用户信息失败');
        }
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err instanceof Error ? err.message : '未知错误');
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]); // 仅在 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>
    </div>
  );
}

useContext

访问 Context 中的数据:

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

// 创建 Context
const ThemeContext = createContext<{
  theme: string;
  toggleTheme: () => void;
}>({
  theme: 'light',
  toggleTheme: () => {},
});

// Provider 组件
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('light');

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

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 使用 Context
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      className={`btn-${theme}`}
    >
      当前主题: {theme},点击切换
    </button>
  );
}

useReducer

管理复杂状态逻辑:

import { useReducer } from 'react';

interface State {
  count: number;
  step: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  | { type: 'setStep'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { ...state, count: 0 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

  return (
    <div>
      <p>计数: {state.count}</p>
      <p>步长: {state.step}</p>
      <input
        type="number"
        value={state.step}
        onChange={(e) => dispatch({
          type: 'setStep',
          payload: Number(e.target.value)
        })}
      />
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  );
}

自定义 Hooks

将组件逻辑提取到可复用的函数中:

import { useState, useEffect } from 'react';

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

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function
        ? value(storedValue)
        : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error('写入 localStorage 失败:', error);
    }
  };

  return [storedValue, setValue] as const;
}

// 使用示例
function App() {
  const [name, setName] = useLocalStorage('name', '');
  const [age, setAge] = useLocalStorage('age', 0);

  return (
    <div>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="输入姓名"
      />
      <input
        type="number"
        value={age}
        onChange={e => setAge(Number(e.target.value))}
        placeholder="输入年龄"
      />
      <p>姓名: {name}, 年龄: {age}</p>
    </div>
  );
}

Hooks 使用规则

  1. 只在顶层调用 - 不要在循环、条件或嵌套函数中调用 Hooks
  2. 只在 React 函数中调用 - 不要在普通 JavaScript 函数中调用
  3. use 开头命名 - 自定义 Hooks 必须以 use 开头

总结

React Hooks 让函数组件拥有了类组件的所有能力,同时代码更简洁、逻辑更清晰。掌握 Hooks 是现代 React 开发的基础。

通过合理使用 Hooks,我们可以:

  • 更好地组织代码逻辑
  • 提高代码的可复用性
  • 简化组件的状态管理
  • 提升开发效率