📚 现代前端开发 目录
阶段3:React
第 16 / 35 课

Hooks:useState与useEffect

函数组件的灵魂

📖 核心概念

💻 代码实现

核心Hooks实战 ✅ tsx
import { useState, useEffect, useRef, useMemo, useCallback, useContext, createContext } from 'react';

// ===== useState:表单状态管理 =====
function useForm<T extends Record<string, any>>(initialValues: T) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});

  const handleChange = (name: keyof T, value: T[keyof T]) => {
    setValues(prev => ({ ...prev, [name]: value }));
    setErrors(prev => ({ ...prev, [name]: undefined }));
  };

  const reset = () => {
    setValues(initialValues);
    setErrors({});
  };

  return { values, errors, handleChange, setErrors, reset };
}

// ===== useEffect:数据获取与清理 =====
function useFetch<T>(url: string, options?: RequestInit) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);
  const abortRef = useRef<AbortController | null>(null);

  useEffect(() => {
    abortRef.current?.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    setLoading(true);
    fetch(url, { ...options, signal: controller.signal })
      .then(r => { if (!r.ok) throw new Error(\`HTTP \${r.status}\`); return r.json(); })
      .then(setData)
      .catch(err => { if (err.name !== 'AbortError') setError(err); })
      .finally(() => setLoading(false));

    return () => controller.abort(); // 清理:取消请求
  }, [url]);

  return { data, error, loading };
}

// ===== useRef:DOM操作与定时器 =====
function Stopwatch() {
  const [time, setTime] = useState(0);
  const [running, setRunning] = useState(false);
  const intervalRef = useRef<ReturnType<typeof setInterval>>();

  useEffect(() => {
    if (running) {
      intervalRef.current = setInterval(() => setTime(t => t + 10), 10);
    } else {
      clearInterval(intervalRef.current);
    }
    return () => clearInterval(intervalRef.current);
  }, [running]);

  return (
    <div>
      <span>{(time / 1000).toFixed(2)}s</span>
      <button onClick={() => setRunning(!running)}>
        {running ? '暂停' : '开始'}
      </button>
      <button onClick={() => { setRunning(false); setTime(0); }}>重置</button>
    </div>
  );
}
性能Hooks与自定义Hook ✅ tsx
// ===== useMemo与useCallback =====
function SearchableList({ items }: { items: Item[] }) {
  const [query, setQuery] = useState('');
  const [sortBy, setSortBy] = useState<'name' | 'date'>('name');

  // 只在query或items变化时重新计算
  const filtered = useMemo(() =>
    items.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    ),
    [items, query]
  );

  // 只在sortBy变化时重新排序
  const sorted = useMemo(() =>
    [...filtered].sort((a, b) =>
      sortBy === 'name' ? a.name.localeCompare(b.name) : a.date - b.date
    ),
    [filtered, sortBy]
  );

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {sorted.map(item => <ItemRow key={item.id} item={item} />)}
    </div>
  );
}

// ===== useContext:主题系统 =====
interface Theme {
  colors: { primary: string; bg: string; text: string };
  mode: 'light' | 'dark';
  toggle: () => void;
}

const ThemeContext = createContext<Theme>({} as Theme);

function ThemeProvider({ children }: { children: ReactNode }) {
  const [mode, setMode] = useState<'light' | 'dark'>('dark');

  const theme = useMemo(() => ({
    mode,
    toggle: () => setMode(m => m === 'dark' ? 'light' : 'dark'),
    colors: mode === 'dark'
      ? { primary: '#06b6d4', bg: '#0f172a', text: '#e0e0e0' }
      : { primary: '#0891b2', bg: '#ffffff', text: '#1e293b' },
  }), [mode]);

  return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

// ===== 自定义Hook:本地存储 =====
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  const set = useCallback((newValue: T | ((prev: T) => T)) => {
    setValue(prev => {
      const next = typeof newValue === 'function' ? (newValue as (p: T) => T)(prev) : newValue;
      localStorage.setItem(key, JSON.stringify(next));
      return next;
    });
  }, [key]);

  return [value, set] as const;
}

// ===== 自定义Hook:窗口尺寸 =====
function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

  useEffect(() => {
    const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);

  return size;
}

🎯 练习任务

🏆 成就解锁
Hooks大师 — 深入理解React Hooks,用自定义Hook复用逻辑