React 19 全栈开发实战指南

React 完整知识体系与实战教程


目录

  1. [React 概述与发展历程](#React 概述与发展历程)
  2. 核心概念
  3. [JSX 深入](#JSX 深入)
  4. 组件系统
  5. [Props 与 State](#Props 与 State)
  6. 事件处理
  7. 条件渲染与列表渲染
  8. [React Hooks 全解](#React Hooks 全解)
  9. [React 19 新特性](#React 19 新特性)
  10. 组件通信与状态管理
  11. 路由管理
  12. 表单处理
  13. 样式方案
  14. 性能优化
  15. [React 设计模式](#React 设计模式)
  16. [TypeScript 与 React](#TypeScript 与 React)
  17. 测试
  18. [服务端渲染(SSR)与 Next.js](#服务端渲染(SSR)与 Next.js)
  19. [React 生态工具链](#React 生态工具链)
  20. 项目最佳实践

1. React 概述与发展历程

1.1 什么是 React

React 是由 Meta(原 Facebook)维护的开源 JavaScript UI 库,用于构建用户界面。其核心理念是组件化------将 UI 拆分为独立、可复用的组件,每个组件管理自身的状态和渲染逻辑。

React 的核心特点:

  • 声明式编程:描述 UI 应该是什么样子,而非如何操作 DOM
  • 组件化架构:UI 由可复用的组件树构成
  • 单向数据流:数据从父组件流向子组件,使数据追踪更可预测
  • 虚拟 DOM:通过内存中的虚拟 DOM 树进行高效的差异比较和最小化 DOM 更新
  • 跨平台:通过 React Native 可以构建移动端应用

1.2 发展里程碑

版本 时间 重要特性
React 0.3 2013.05 首次开源
React 15 2016.04 虚拟 DOM 重写
React 16 2017.09 Fiber 架构、Error Boundary、Portal
React 16.8 2019.02 Hooks 登场
React 17 2020.10 无新特性,渐进式升级基础
React 18 2022.03 并发渲染、Suspense、自动批处理
React 19 2024.12 Actions API、React Compiler、Server Components 稳定版、use() API
React 19.1 2025.06 迭代优化
React 19.2 2025 下半年 Activity、useEffectEvent 等实验性特性

1.3 核心架构:Fiber

React 16 引入了 Fiber 架构,将渲染工作拆分为可中断的小任务单元(Fiber 节点)。每个 Fiber 节点代表一个组件实例或 DOM 元素,包含该节点的类型、状态、子节点引用等信息。

Fiber 架构的核心优势:

  • 可中断渲染:长时间渲染任务可以被暂停,让出主线程给更高优先级的任务(如用户输入)
  • 优先级调度:不同类型的更新有不同的优先级
  • 并发模式基础:React 18/19 的并发特性都建立在 Fiber 之上

2. 核心概念

2.1 虚拟 DOM(Virtual DOM)

虚拟 DOM 是 React 性能模型的核心。它是真实 DOM 的 JavaScript 对象表示。

工作流程:

  1. 状态变化 → React 创建新的虚拟 DOM 树
  2. Diffing:新旧虚拟 DOM 树进行比较(Reconciliation)
  3. Patch:计算出最小的 DOM 操作集合
  4. Commit:将变更批量应用到真实 DOM

Diff 算法策略:

  • 不同类型的元素产生不同的树(直接替换)
  • 同一层级的子元素通过 key 来标识
  • 开发者可以通过 key 提示哪些子元素在不同渲染中保持稳定
jsx 复制代码
// key 的正确使用
// ✅ 使用稳定的唯一 ID
{items.map(item => <ListItem key={item.id} data={item} />)}

// ❌ 避免使用数组索引作为 key(除非列表不会重新排序)
{items.map((item, index) => <ListItem key={index} data={item} />)}

2.2 单向数据流

React 采用单向数据流(Unidirectional Data Flow):

复制代码
父组件 State
    ↓ (props)
子组件 → 渲染 UI
    ↓ (事件回调)
触发父组件 State 更新 → 重新渲染

好处:数据流向可预测,便于调试和追踪问题。

2.3 渲染流程

React 的渲染分为三个阶段:

  1. 触发(Trigger):状态变更、props 变更、父组件重渲染、Context 变更
  2. 渲染(Render):调用组件函数,生成虚拟 DOM(纯计算阶段,无副作用)
  3. 提交(Commit):将变更应用到真实 DOM,然后运行 effects

3. JSX 深入

3.1 JSX 是什么

JSX(JavaScript XML)是 JavaScript 的语法扩展,允许在 JavaScript 中编写类 HTML 的结构。JSX 不是模板语言,而是语法糖------最终会被编译为 React.createElement() 调用(React 17+ 使用新的 JSX Transform)。

jsx 复制代码
// JSX 写法
const element = <h1 className="title">Hello, React!</h1>;

// 编译后(旧版 transform)
const element = React.createElement('h1', { className: 'title' }, 'Hello, React!');

// 编译后(React 17+ 新 JSX Transform)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { className: 'title', children: 'Hello, React!' });

3.2 JSX 核心规则

jsx 复制代码
function MyComponent() {
  const isLoggedIn = true;
  const items = ['Apple', 'Banana', 'Cherry'];
  const userInput = '<script>alert("xss")</script>';

  return (
    // 规则1: 必须有一个根元素(或使用 Fragment)
    <>
      {/* 规则2: 使用 className 代替 class */}
      <div className="container">

        {/* 规则3: 使用 htmlFor 代替 for */}
        <label htmlFor="name">Name:</label>

        {/* 规则4: JSX 中的 JS 表达式用 {} 包裹 */}
        <p>{1 + 1}</p>

        {/* 规则5: 条件渲染 */}
        {isLoggedIn && <p>Welcome back!</p>}
        {isLoggedIn ? <Dashboard /> : <Login />}

        {/* 规则6: 列表渲染 */}
        <ul>
          {items.map((item, index) => (
            <li key={item}>{item}</li>
          ))}
        </ul>

        {/* 规则7: style 使用对象语法 */}
        <div style={{ backgroundColor: 'blue', fontSize: '16px' }}>Styled</div>

        {/* 规则8: 自动 XSS 防护 - HTML 会被转义 */}
        <p>{userInput}</p> {/* 安全输出为文本 */}

        {/* 规则9: 自闭合标签必须闭合 */}
        <img src="photo.jpg" alt="Photo" />
        <br />
        <input type="text" />
      </div>
    </>
  );
}

3.3 Fragment

Fragment 允许组件返回多个元素而不需要额外的包裹 DOM 节点:

jsx 复制代码
// 完整语法(支持 key)
import { Fragment } from 'react';

function Glossary({ items }) {
  return items.map(item => (
    <Fragment key={item.id}>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  ));
}

// 简写语法(不支持 key)
function App() {
  return (
    <>
      <Header />
      <Main />
      <Footer />
    </>
  );
}

4. 组件系统

4.1 函数组件(推荐)

从 React 16.8 引入 Hooks 之后,函数组件成为 React 开发的标准方式。

jsx 复制代码
// 基本函数组件
function Welcome({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// 箭头函数写法
const Welcome = ({ name }) => <h1>Hello, {name}!</h1>;

// 默认导出
export default function App() {
  return <Welcome name="React" />;
}

4.2 类组件(了解即可)

类组件是 React 早期的写法,现仍可使用但不推荐用于新项目。

jsx 复制代码
import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  // 生命周期方法
  componentDidMount() {
    console.log('组件已挂载');
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      console.log('count 已更新');
    }
  }

  componentWillUnmount() {
    console.log('组件即将卸载');
  }

  handleClick = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    );
  }
}

4.3 组件设计原则

  • 单一职责:每个组件只做一件事
  • 可复用性:通过 props 使组件通用化
  • 组合优于继承 :使用 children 和组合模式而非继承
  • 受控与非受控:明确组件的状态由谁管理
jsx 复制代码
// 组合模式示例
function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

function App() {
  return (
    <Card title="User Profile">
      <Avatar />
      <UserInfo />
    </Card>
  );
}

5. Props 与 State

5.1 Props

Props(Properties)是组件之间传递数据的方式,只读,不可在子组件中修改。

jsx 复制代码
// Props 传递
function UserCard({ name, age, avatar, onFollow }) {
  return (
    <div>
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>Age: {age}</p>
      <button onClick={onFollow}>Follow</button>
    </div>
  );
}

// 使用组件
<UserCard
  name="Alice"
  age={25}
  avatar="/alice.jpg"
  onFollow={() => console.log('Followed!')}
/>

// 默认 Props
function Button({ variant = 'primary', size = 'md', children }) {
  return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}

// 展开 Props
function Wrapper(props) {
  return <InnerComponent {...props} />;
}

// children prop
function Layout({ children }) {
  return (
    <div className="layout">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

5.2 State

State 是组件的内部可变数据。State 的更新是异步的,且不可直接修改。

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

function Form() {
  // 基本类型
  const [name, setName] = useState('');
  const [count, setCount] = useState(0);
  const [isOpen, setIsOpen] = useState(false);

  // 对象类型 ------ 更新时必须创建新对象
  const [user, setUser] = useState({ name: '', email: '' });
  const updateName = (newName) => {
    setUser(prev => ({ ...prev, name: newName })); // ✅ 创建新对象
    // user.name = newName;  // ❌ 直接修改是错误的
  };

  // 数组类型
  const [items, setItems] = useState([]);
  const addItem = (item) => setItems(prev => [...prev, item]);           // 添加
  const removeItem = (id) => setItems(prev => prev.filter(i => i.id !== id)); // 删除
  const updateItem = (id, data) => setItems(prev =>
    prev.map(i => i.id === id ? { ...i, ...data } : i)                   // 更新
  );

  // 函数式更新 ------ 当新状态依赖旧状态时使用
  const increment = () => setCount(prev => prev + 1);

  // 惰性初始化 ------ 仅在首次渲染时执行
  const [config] = useState(() => {
    return JSON.parse(localStorage.getItem('config') || '{}');
  });
}

5.3 Props vs State 对比

特性 Props State
所有者 父组件 当前组件
可变性 只读 可通过 setter 更新
用途 组件间通信 组件内部状态管理
更新触发渲染 父组件更新时 调用 setter 时

6. 事件处理

6.1 基本事件处理

React 使用合成事件(SyntheticEvent),它是对原生浏览器事件的跨浏览器包装。

jsx 复制代码
function EventExample() {
  // 基本点击事件
  const handleClick = (e) => {
    e.preventDefault(); // 阻止默认行为
    e.stopPropagation(); // 阻止冒泡
    console.log('Clicked!', e.target);
  };

  // 传参
  const handleDelete = (id) => {
    console.log('Delete item:', id);
  };

  // 输入事件
  const handleChange = (e) => {
    console.log('Input value:', e.target.value);
  };

  // 键盘事件
  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  return (
    <div>
      <button onClick={handleClick}>Click Me</button>
      <button onClick={() => handleDelete(42)}>Delete</button>
      <input onChange={handleChange} onKeyDown={handleKeyDown} />
    </div>
  );
}

6.2 常用事件类型

事件类别 事件名
鼠标 onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseMove
键盘 onKeyDown, onKeyUp, onKeyPress
表单 onChange, onSubmit, onFocus, onBlur, onInput
触摸 onTouchStart, onTouchMove, onTouchEnd
剪贴板 onCopy, onCut, onPaste
滚动 onScroll
拖拽 onDrag, onDragStart, onDragEnd, onDrop

7. 条件渲染与列表渲染

7.1 条件渲染

jsx 复制代码
function Dashboard({ user, notifications, role }) {
  // 1. if-else(适合复杂逻辑)
  if (!user) {
    return <LoginPage />;
  }

  // 2. 三元运算符(适合二选一)
  const greeting = user.isNew ? <Welcome /> : <WelcomeBack />;

  // 3. && 短路运算(适合有/无)
  const badge = notifications.length > 0 && <Badge count={notifications.length} />;

  // 4. 多条件映射
  const roleComponent = {
    admin: <AdminPanel />,
    editor: <EditorPanel />,
    viewer: <ViewerPanel />,
  }[role] || <DefaultPanel />;

  return (
    <div>
      {greeting}
      {badge}
      {roleComponent}
      {/* 5. IIFE 模式(复杂内联逻辑) */}
      {(() => {
        if (user.isPremium) return <PremiumFeatures />;
        if (user.isTrial) return <TrialBanner />;
        return <UpgradePrompt />;
      })()}
    </div>
  );
}

7.2 列表渲染

jsx 复制代码
function TodoList({ todos }) {
  if (todos.length === 0) {
    return <p>No todos yet!</p>;
  }

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id} className={todo.done ? 'completed' : ''}>
          <span>{todo.text}</span>
          <small>{todo.createdAt}</small>
        </li>
      ))}
    </ul>
  );
}

// 嵌套列表
function CategoryList({ categories }) {
  return categories.map(category => (
    <section key={category.id}>
      <h2>{category.name}</h2>
      <ul>
        {category.items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </section>
  ));
}

Key 的最佳实践:

  • 使用数据中稳定、唯一的 ID
  • 不要用 Math.random() 生成 key
  • 列表不会重排序时可用索引,但仍建议用 ID
  • key 只在兄弟元素间需要唯一,不需要全局唯一

8. React Hooks 全解

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

8.1 Hooks 规则

  1. 只在函数组件或自定义 Hook 的顶层调用 Hooks
  2. 不要在循环、条件或嵌套函数中调用 Hooks
  3. 自定义 Hook 必须以 use 开头
jsx 复制代码
function MyComponent({ condition }) {
  // ✅ 正确
  const [value, setValue] = useState(0);

  // ❌ 错误 - 不要在条件中调用
  // if (condition) {
  //   const [other, setOther] = useState('');
  // }
}

8.2 useState ------ 状态管理

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

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

  // 对象状态
  const [form, setForm] = useState({
    username: '',
    email: '',
    password: '',
  });

  // 数组状态
  const [todos, setTodos] = useState([]);

  // 函数式更新(基于前一个状态计算新状态)
  const increment = () => setCount(prev => prev + 1);

  // 批量更新多次调用
  const incrementThree = () => {
    setCount(prev => prev + 1); // 0 → 1
    setCount(prev => prev + 1); // 1 → 2
    setCount(prev => prev + 1); // 2 → 3
  };

  // 更新对象中的某个字段
  const handleInputChange = (field) => (e) => {
    setForm(prev => ({
      ...prev,
      [field]: e.target.value,
    }));
  };

  // 惰性初始化
  const [data] = useState(() => expensiveComputation());

  return (
    <form>
      <input value={form.username} onChange={handleInputChange('username')} />
      <input value={form.email} onChange={handleInputChange('email')} />
      <p>Count: {count}</p>
      <button type="button" onClick={increment}>+1</button>
    </form>
  );
}

8.3 useEffect ------ 副作用管理

useEffect 用于处理副作用:数据获取、订阅、DOM 操作等。

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

function EffectExamples({ userId }) {
  const [user, setUser] = useState(null);
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  // 1. 每次渲染后执行(无依赖数组)
  useEffect(() => {
    document.title = `User: ${user?.name || 'Loading'}`;
  });

  // 2. 仅在挂载时执行(空依赖数组)
  useEffect(() => {
    console.log('Component mounted');
    return () => console.log('Component unmounted'); // 清理函数
  }, []);

  // 3. 依赖变化时执行
  useEffect(() => {
    let cancelled = false; // 防止竞态条件

    async function fetchUser() {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      if (!cancelled) {
        setUser(data);
      }
    }

    fetchUser();

    // 清理函数:组件卸载或 userId 变化时执行
    return () => {
      cancelled = true;
    };
  }, [userId]);

  // 4. 事件订阅与清理
  useEffect(() => {
    const handleResize = () => setWindowWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  // 5. 定时器
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick');
    }, 1000);
    return () => clearInterval(timer);
  }, []);
}

useEffect 依赖数组总结:

形式 执行时机
useEffect(fn) 每次渲染后
useEffect(fn, []) 仅挂载和卸载
useEffect(fn, [a, b]) 挂载 + a 或 b 变化时

8.4 useContext ------ 上下文

跨层级共享数据,避免 props 逐层传递(prop drilling)。

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

// 1. 创建 Context
const ThemeContext = createContext('light');

// 2. 提供 Provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

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

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

// 3. 在任意后代组件中消费
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'dark' ? '#333' : '#fff',
        color: theme === 'dark' ? '#fff' : '#333',
      }}
    >
      Current: {theme}
    </button>
  );
}

// 4. 在 App 中使用
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Main />
      <ThemedButton />
    </ThemeProvider>
  );
}

8.5 useReducer ------ 复杂状态逻辑

适用于状态逻辑复杂、包含多个子值、下一个状态依赖前一个状态的场景。

jsx 复制代码
import { useReducer } from 'react';

// 定义 action 类型
const ACTIONS = {
  ADD_TODO: 'ADD_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO',
  DELETE_TODO: 'DELETE_TODO',
  EDIT_TODO: 'EDIT_TODO',
};

// Reducer 函数(纯函数)
function todoReducer(state, action) {
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return [
        ...state,
        { id: Date.now(), text: action.payload, completed: false },
      ];
    case ACTIONS.TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case ACTIONS.DELETE_TODO:
      return state.filter(todo => todo.id !== action.payload);
    case ACTIONS.EDIT_TODO:
      return state.map(todo =>
        todo.id === action.payload.id
          ? { ...todo, text: action.payload.text }
          : todo
      );
    default:
      return state;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);

  const addTodo = (text) => {
    dispatch({ type: ACTIONS.ADD_TODO, payload: text });
  };

  const toggleTodo = (id) => {
    dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id });
  };

  return (
    <div>
      <button onClick={() => addTodo('New Task')}>Add</button>
      {todos.map(todo => (
        <div key={todo.id} onClick={() => toggleTodo(todo.id)}>
          {todo.completed ? '✅' : '⬜'} {todo.text}
        </div>
      ))}
    </div>
  );
}

8.6 useRef ------ 引用

useRef 返回一个可变的 ref 对象,其 .current 属性在组件的整个生命周期内持久存在,修改它不会触发重新渲染。

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

function RefExamples() {
  // 1. 访问 DOM 元素
  const inputRef = useRef(null);
  const focusInput = () => inputRef.current.focus();

  // 2. 存储可变值(不触发重渲染)
  const renderCount = useRef(0);
  useEffect(() => {
    renderCount.current += 1;
  });

  // 3. 保存前一个值
  const [count, setCount] = useState(0);
  const prevCount = useRef(count);
  useEffect(() => {
    prevCount.current = count;
  }, [count]);

  // 4. 存储定时器 ID
  const timerRef = useRef(null);
  const startTimer = () => {
    timerRef.current = setInterval(() => console.log('tick'), 1000);
  };
  const stopTimer = () => clearInterval(timerRef.current);

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
      <p>Render count: {renderCount.current}</p>
      <p>Current: {count}, Previous: {prevCount.current}</p>
    </div>
  );
}

8.7 useMemo 与 useCallback ------ 性能优化

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

function OptimizedComponent({ items, onItemClick }) {
  const [filter, setFilter] = useState('');

  // useMemo: 缓存计算结果
  const filteredItems = useMemo(() => {
    console.log('Filtering items...'); // 仅在 items 或 filter 变化时执行
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // useMemo: 缓存复杂对象
  const chartData = useMemo(() => {
    return processDataForChart(items);
  }, [items]);

  // useCallback: 缓存函数引用
  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);

  // useCallback: 配合 React.memo 的子组件使用
  const handleDelete = useCallback((id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  }, []);

  return (
    <div>
      <input value={filter} onChange={e => setFilter(e.target.value)} />
      {filteredItems.map(item => (
        <MemoizedItem
          key={item.id}
          item={item}
          onClick={handleClick}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}

// React.memo 包裹的子组件
const MemoizedItem = React.memo(function Item({ item, onClick, onDelete }) {
  return (
    <div>
      <span onClick={() => onClick(item.id)}>{item.name}</span>
      <button onClick={() => onDelete(item.id)}>Delete</button>
    </div>
  );
});

何时使用 useMemo / useCallback:

  • 计算开销大的操作(大列表过滤、排序、复杂数学运算)
  • 传递给被 React.memo 包裹的子组件的 props
  • 作为其他 Hooks 的依赖项
  • 不要过度使用------简单计算的 memo 开销可能大于重复计算

8.8 useImperativeHandle

自定义通过 ref 暴露给父组件的实例值。

jsx 复制代码
import { forwardRef, useImperativeHandle, useRef } from 'react';

const FancyInput = forwardRef(function FancyInput(props, ref) {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current.focus();
    },
    scrollIntoView() {
      inputRef.current.scrollIntoView({ behavior: 'smooth' });
    },
    getValue() {
      return inputRef.current.value;
    },
  }), []);

  return <input ref={inputRef} {...props} />;
});

// 父组件使用
function Parent() {
  const fancyRef = useRef();

  return (
    <div>
      <FancyInput ref={fancyRef} />
      <button onClick={() => fancyRef.current.focus()}>Focus Input</button>
    </div>
  );
}

注意:React 19 中,ref 可以直接作为 props 传递给函数组件,不再必须使用 forwardRef。

8.9 useLayoutEffect

与 useEffect 类似,但在 DOM 更新后、浏览器绘制前同步执行。适用于需要读取 DOM 布局并同步触发重渲染的场景。

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

function Tooltip({ children, text }) {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const tooltipRef = useRef();

  // 在浏览器绘制前测量 DOM
  useLayoutEffect(() => {
    const { height } = tooltipRef.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  return (
    <div>
      <div ref={tooltipRef} style={{ position: 'absolute', top: -tooltipHeight }}>
        {text}
      </div>
      {children}
    </div>
  );
}

8.10 其他内置 Hooks

jsx 复制代码
// useId - 生成稳定的唯一 ID(SSR 安全)
import { useId } from 'react';
function FormField({ label }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

// useTransition - 标记非紧急更新
import { useTransition, useState } from 'react';
function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setQuery(e.target.value); // 紧急更新
    startTransition(() => {
      setResults(filterResults(e.target.value)); // 非紧急更新
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultList results={results} />
    </div>
  );
}

// useDeferredValue - 延迟更新值
import { useDeferredValue } from 'react';
function FilteredList({ filter }) {
  const deferredFilter = useDeferredValue(filter);
  // deferredFilter 在高优先级更新完成后才更新
  const items = useMemo(() => filterItems(deferredFilter), [deferredFilter]);
  return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}

// useSyncExternalStore - 订阅外部 store
import { useSyncExternalStore } from 'react';
function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    () => navigator.onLine, // 客户端快照
    () => true              // 服务端快照
  );
}

8.11 自定义 Hook

自定义 Hook 是以 use 开头的函数,可以内部调用其他 Hook,用于逻辑复用。

jsx 复制代码
// useFetch - 通用数据获取 Hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then(json => {
        if (!cancelled) {
          setData(json);
          setError(null);
        }
      })
      .catch(err => {
        if (!cancelled) setError(err.message);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// useLocalStorage - 持久化状态
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value) => {
    const valueToStore = value instanceof Function ? value(storedValue) : value;
    setStoredValue(valueToStore);
    localStorage.setItem(key, JSON.stringify(valueToStore));
  };

  return [storedValue, setValue];
}

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

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

  return debouncedValue;
}

// useMediaQuery - 响应式
function useMediaQuery(query) {
  const [matches, setMatches] = useState(
    () => window.matchMedia(query).matches
  );

  useEffect(() => {
    const mql = window.matchMedia(query);
    const handler = (e) => setMatches(e.matches);
    mql.addEventListener('change', handler);
    return () => mql.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

// 使用示例
function App() {
  const { data, loading, error } = useFetch('/api/posts');
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const isMobile = useMediaQuery('(max-width: 768px)');

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage message={error} />;
  return <PostList posts={data} layout={isMobile ? 'stack' : 'grid'} />;
}

9. React 19 新特性

React 19 于 2024 年 12 月正式发布,是一次重要的大版本更新。

9.1 Actions API

Actions 简化了异步数据变更的处理,自动管理 pending 状态、错误处理和乐观更新。

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

function UpdateProfile() {
  const [name, setName] = useState('');
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () => {
    startTransition(async () => {
      const result = await updateProfileAPI(name);
      if (result.error) {
        setError(result.error);
      }
    });
  };

  return (
    <div>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        {isPending ? 'Saving...' : 'Save'}
      </button>
      {error && <p className="error">{error}</p>}
    </div>
  );
}

9.2 useActionState

用于管理表单 action 的状态:

jsx 复制代码
import { useActionState } from 'react';

function LoginForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      const email = formData.get('email');
      const password = formData.get('password');
      try {
        await loginAPI(email, password);
        return { success: true, error: null };
      } catch (err) {
        return { success: false, error: err.message };
      }
    },
    { success: false, error: null } // 初始状态
  );

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button disabled={isPending}>
        {isPending ? 'Logging in...' : 'Log In'}
      </button>
      {state.error && <p className="error">{state.error}</p>}
      {state.success && <p className="success">Welcome!</p>}
    </form>
  );
}

9.3 useOptimistic

实现乐观 UI 更新------在服务端确认前先更新 UI:

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

function MessageList({ messages, sendMessage }) {
  const [optimisticMessages, addOptimistic] = useOptimistic(
    messages,
    (currentMessages, newMessage) => [
      ...currentMessages,
      { ...newMessage, sending: true },
    ]
  );

  const handleSend = async (text) => {
    const newMsg = { id: Date.now(), text };
    addOptimistic(newMsg); // 立即显示
    await sendMessage(newMsg); // 发送到服务端
  };

  return (
    <ul>
      {optimisticMessages.map(msg => (
        <li key={msg.id} style={{ opacity: msg.sending ? 0.6 : 1 }}>
          {msg.text}
          {msg.sending && ' (sending...)'}
        </li>
      ))}
    </ul>
  );
}

9.4 use() API

新的 use API 可以在渲染期间读取 Promise 和 Context:

jsx 复制代码
import { use, Suspense } from 'react';

// 读取 Promise
function UserProfile({ userPromise }) {
  const user = use(userPromise); // React 会 Suspend 直到 Promise resolve
  return <h1>{user.name}</h1>;
}

function App() {
  const userPromise = fetchUser(1); // 在外部发起请求

  return (
    <Suspense fallback={<Loading />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

// 条件性读取 Context(这是 use 相比 useContext 的优势)
function StatusMessage({ isEnabled }) {
  if (isEnabled) {
    const theme = use(ThemeContext); // 可以在条件内调用
    return <p style={{ color: theme.primary }}>Enabled</p>;
  }
  return <p>Disabled</p>;
}

9.5 React Compiler(实验性)

React Compiler 在构建时自动优化组件,减少不必要的重渲染,开发者无需手动编写 useMemo、useCallback 等。

jsx 复制代码
// 之前需要手动优化
const MemoizedComponent = React.memo(function Component({ data }) {
  const processed = useMemo(() => expensiveCalc(data), [data]);
  const handler = useCallback(() => doSomething(data), [data]);
  return <Child data={processed} onClick={handler} />;
});

// React Compiler 之后,直接写即可
function Component({ data }) {
  const processed = expensiveCalc(data);
  const handler = () => doSomething(data);
  return <Child data={processed} onClick={handler} />;
}
// 编译器自动决定何处需要 memoization

9.6 Server Components

Server Components 在服务端运行,不向客户端发送 JavaScript,用于数据获取和静态内容渲染。

jsx 复制代码
// ServerComponent.jsx (默认是 Server Component)
// 可以直接使用 async/await
async function BlogPost({ id }) {
  const post = await db.posts.findOne({ id }); // 直接访问数据库
  const comments = await db.comments.find({ postId: id });

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <CommentList comments={comments} />
      <AddComment postId={id} /> {/* 客户端组件 */}
    </article>
  );
}

// AddComment.jsx
'use client' // 标记为客户端组件

import { useState } from 'react';

function AddComment({ postId }) {
  const [text, setText] = useState('');
  // 可以使用 Hooks、事件处理、浏览器 API
  return (
    <form onSubmit={handleSubmit}>
      <textarea value={text} onChange={e => setText(e.target.value)} />
      <button>Submit</button>
    </form>
  );
}

Server Components vs Client Components:

特性 Server Components Client Components
运行环境 服务端 客户端
指令 默认 'use client'
可用 Hooks
可用浏览器 API
直接访问数据库
打包到客户端 JS
可使用 async/await 否(需在 useEffect 中)

9.7 其他 React 19 改进

  • ref 作为 prop:函数组件可直接接收 ref,无需 forwardRef
  • ref cleanup 函数:ref 回调可返回清理函数
  • 文档元数据支持 :直接在组件中渲染 <title>, <meta>, <link>,React 自动提升到 <head>
  • 资源预加载 APIprefetchDNS, preconnect, preload, preinit
  • 样式表优先级<link>precedence 属性控制样式表加载顺序
  • 增强的错误报告:更好的 hydration 错误信息
jsx 复制代码
// ref 作为 prop(React 19)
function MyInput({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// ref cleanup
<div ref={(node) => {
  // 设置 ref
  node.addEventListener('scroll', handleScroll);
  // 返回清理函数
  return () => node.removeEventListener('scroll', handleScroll);
}} />

// 文档元数据
function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="description" content={post.excerpt} />
      <link rel="canonical" href={post.url} />
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

10. 组件通信与状态管理

10.1 组件通信方式总结

方式 适用场景
Props 父 → 子
回调函数 Props 子 → 父
Context 跨层级共享
Ref 父访问子组件实例/DOM
状态提升 兄弟组件共享数据
状态管理库 复杂全局状态

10.2 状态提升

当多个组件需要共享状态时,将状态提升到最近的共同祖先:

jsx 复制代码
function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  const fahrenheit = celsius ? (parseFloat(celsius) * 9/5 + 32).toFixed(1) : '';

  return (
    <div>
      <TemperatureInput
        scale="Celsius"
        value={celsius}
        onChange={setCelsius}
      />
      <TemperatureInput
        scale="Fahrenheit"
        value={fahrenheit}
        onChange={(f) => setCelsius(((parseFloat(f) - 32) * 5/9).toFixed(1))}
      />
    </div>
  );
}

10.3 useReducer + Context 全局状态

适合中等复杂度的应用,无需引入外部库:

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

// 定义状态和 actions
const initialState = {
  user: null,
  theme: 'light',
  notifications: [],
};

function appReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.payload };
    case 'LOGOUT':
      return { ...state, user: null };
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'ADD_NOTIFICATION':
      return { ...state, notifications: [...state.notifications, action.payload] };
    default:
      return state;
  }
}

// 创建 Context
const AppContext = createContext();
const AppDispatchContext = createContext();

// Provider
function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);
  return (
    <AppContext.Provider value={state}>
      <AppDispatchContext.Provider value={dispatch}>
        {children}
      </AppDispatchContext.Provider>
    </AppContext.Provider>
  );
}

// 自定义 Hooks 简化使用
function useAppState() {
  return useContext(AppContext);
}

function useAppDispatch() {
  return useContext(AppDispatchContext);
}

// 在组件中使用
function UserMenu() {
  const { user } = useAppState();
  const dispatch = useAppDispatch();

  if (!user) return <button onClick={() => dispatch({ type: 'LOGIN', payload: { name: 'Alice' } })}>Login</button>;
  return <span>{user.name} <button onClick={() => dispatch({ type: 'LOGOUT' })}>Logout</button></span>;
}

10.4 主流状态管理库对比

特点 适用场景
Zustand 极简 API,不需要 Provider 中小型项目,替代 Redux
Redux Toolkit 成熟生态,DevTools 强大 大型项目,需要严格状态管理
Jotai 原子化状态,自下而上 细粒度状态,避免不必要重渲染
Recoil Facebook 出品,原子 + 选择器 复杂派生状态
TanStack Query 专注服务端状态(缓存、同步) 数据获取密集型应用
Valtio 基于 Proxy 的可变风格 偏好可变编程风格
jsx 复制代码
// Zustand 示例
import { create } from 'zustand';

const useStore = create((set, get) => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  // 异步 action
  fetchCount: async () => {
    const res = await fetch('/api/count');
    const data = await res.json();
    set({ count: data.count });
  },
}));

function Counter() {
  const { count, increment, decrement } = useStore();
  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}
jsx 复制代码
// TanStack Query 示例
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function Posts() {
  const queryClient = useQueryClient();

  // 查询
  const { data: posts, isLoading, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
    staleTime: 5 * 60 * 1000,   // 5 分钟内不重新请求
    gcTime: 30 * 60 * 1000,     // 缓存保留 30 分钟
  });

  // 变更
  const mutation = useMutation({
    mutationFn: (newPost) => fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify(newPost),
    }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] }); // 自动刷新
    },
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;

  return (
    <div>
      {posts.map(post => <PostCard key={post.id} post={post} />)}
      <button onClick={() => mutation.mutate({ title: 'New Post' })}>
        Add Post
      </button>
    </div>
  );
}

11. 路由管理

11.1 React Router v6+

jsx 复制代码
import { BrowserRouter, Routes, Route, Link, NavLink, Outlet, useParams, useNavigate, useSearchParams } from 'react-router-dom';

// 基本路由配置
function App() {
  return (
    <BrowserRouter>
      <nav>
        <NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
          Home
        </NavLink>
        <NavLink to="/about">About</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />

        {/* 嵌套路由 */}
        <Route path="/users" element={<UsersLayout />}>
          <Route index element={<UserList />} />
          <Route path=":userId" element={<UserDetail />} />
          <Route path=":userId/edit" element={<UserEdit />} />
        </Route>

        {/* 受保护路由 */}
        <Route path="/dashboard" element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } />

        {/* 404 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 嵌套路由布局
function UsersLayout() {
  return (
    <div className="users-layout">
      <h1>Users</h1>
      <Outlet /> {/* 子路由在此渲染 */}
    </div>
  );
}

// 路由参数
function UserDetail() {
  const { userId } = useParams();
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();

  const tab = searchParams.get('tab') || 'profile';

  return (
    <div>
      <h2>User {userId}</h2>
      <button onClick={() => navigate(-1)}>Back</button>
      <button onClick={() => navigate('/users')}>User List</button>
      <button onClick={() => setSearchParams({ tab: 'settings' })}>
        Settings
      </button>
    </div>
  );
}

// 路由守卫
function ProtectedRoute({ children }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// 代码分割 + 路由懒加载
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

12. 表单处理

12.1 受控组件

jsx 复制代码
function RegistrationForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    password: '',
    role: 'user',
    agree: false,
  });
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value,
    }));
    // 清除错误
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };

  const validate = () => {
    const newErrors = {};
    if (!formData.name.trim()) newErrors.name = 'Name is required';
    if (!/^\S+@\S+\.\S+$/.test(formData.email)) newErrors.email = 'Invalid email';
    if (formData.password.length < 8) newErrors.password = 'Min 8 characters';
    if (!formData.agree) newErrors.agree = 'Must agree to terms';
    return newErrors;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const validationErrors = validate();
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }
    console.log('Submit:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>

      <div>
        <input name="email" type="email" value={formData.email} onChange={handleChange} />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <div>
        <input name="password" type="password" value={formData.password} onChange={handleChange} />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>

      <select name="role" value={formData.role} onChange={handleChange}>
        <option value="user">User</option>
        <option value="admin">Admin</option>
      </select>

      <label>
        <input name="agree" type="checkbox" checked={formData.agree} onChange={handleChange} />
        I agree to the terms
        {errors.agree && <span className="error">{errors.agree}</span>}
      </label>

      <button type="submit">Register</button>
    </form>
  );
}

12.2 非受控组件

jsx 复制代码
function UncontrolledForm() {
  const nameRef = useRef();
  const fileRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Name:', nameRef.current.value);
    console.log('File:', fileRef.current.files[0]);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} defaultValue="default" />
      <input ref={fileRef} type="file" />
      <button>Submit</button>
    </form>
  );
}

12.3 使用 React Hook Form

对于复杂表单,推荐使用 React Hook Form 等库减少样板代码:

jsx 复制代码
import { useForm } from 'react-hook-form';

function FormWithLibrary() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch,
    reset,
  } = useForm({
    defaultValues: { name: '', email: '' },
  });

  const onSubmit = async (data) => {
    await submitToAPI(data);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('name', {
          required: 'Name is required',
          minLength: { value: 2, message: 'Min 2 characters' },
        })}
      />
      {errors.name && <span>{errors.name.message}</span>}

      <input
        {...register('email', {
          required: 'Email is required',
          pattern: { value: /^\S+@\S+$/i, message: 'Invalid email' },
        })}
      />
      {errors.email && <span>{errors.email.message}</span>}

      <button disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

13. 样式方案

13.1 方案对比

方案 优点 缺点
CSS Modules 局部作用域、零运行时 需要构建工具支持
Tailwind CSS 快速开发、一致性 类名较长
CSS-in-JS (styled-components) 动态样式、组件封装 运行时开销
Vanilla CSS / Sass 熟悉、简单 全局命名冲突

13.2 CSS Modules

jsx 复制代码
// Button.module.css
// .button { padding: 8px 16px; border-radius: 4px; }
// .primary { background: #007bff; color: white; }
// .secondary { background: #6c757d; color: white; }

import styles from './Button.module.css';

function Button({ variant = 'primary', children }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

13.3 Tailwind CSS

jsx 复制代码
function Card({ title, children }) {
  return (
    <div className="rounded-lg shadow-md p-6 bg-white dark:bg-gray-800
                    hover:shadow-lg transition-shadow duration-300">
      <h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
        {title}
      </h2>
      <div className="text-gray-600 dark:text-gray-300">
        {children}
      </div>
    </div>
  );
}

13.4 styled-components

jsx 复制代码
import styled from 'styled-components';

const StyledButton = styled.button`
  padding: 8px 16px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
  font-size: ${props => props.size === 'lg' ? '18px' : '14px'};
  background: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
  color: white;

  &:hover {
    opacity: 0.9;
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

function App() {
  return <StyledButton variant="primary" size="lg">Click Me</StyledButton>;
}

14. 性能优化

14.1 理解重渲染

组件重渲染的触发条件:

  1. 自身状态变化
  2. 父组件重渲染(即使子组件 props 未变)
  3. 所消费的 Context 值变化
  4. 自定义 Hook 中的状态变化

14.2 避免不必要的重渲染

jsx 复制代码
import { memo, useMemo, useCallback } from 'react';

// 1. React.memo ------ 浅比较 props
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
  return items.map(item => (
    <div key={item.id} onClick={() => onSelect(item.id)}>
      {item.name}
    </div>
  ));
});

// 2. 自定义比较函数
const DeepMemoComponent = memo(
  function MyComponent({ config }) {
    return <div>{config.title}</div>;
  },
  (prevProps, nextProps) => {
    return prevProps.config.id === nextProps.config.id;
  }
);

// 3. 状态下沉 ------ 将频繁变化的状态放在更低层级
// ❌ 不好:整个页面因为鼠标位置更新
function Page() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  return (
    <div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
      <Cursor position={position} />
      <ExpensiveComponent /> {/* 不必要地重渲染 */}
    </div>
  );
}

// ✅ 好:将状态隔离到子组件
function Page() {
  return (
    <div>
      <MouseTracker /> {/* 状态在这里面 */}
      <ExpensiveComponent /> {/* 不受影响 */}
    </div>
  );
}

14.3 代码分割与懒加载

jsx 复制代码
import { lazy, Suspense, startTransition } from 'react';

// 路由级代码分割
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

// 带 prefetch 的懒加载
const AdminPanel = lazy(() => import(/* webpackPrefetch: true */ './pages/Admin'));

// Suspense 边界
function App() {
  return (
    <Suspense fallback={<FullPageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={
          <Suspense fallback={<DashboardSkeleton />}>
            <Dashboard />
          </Suspense>
        } />
      </Routes>
    </Suspense>
  );
}

14.4 列表虚拟化

渲染大量列表时,只渲染可视区域内的元素:

jsx 复制代码
import { FixedSizeList as List } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style} className="list-row">
      {items[index].name}
    </div>
  );

  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </List>
  );
}

14.5 并发特性优化

jsx 复制代码
import { useTransition, useDeferredValue } from 'react';

function SearchWithTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value); // 高优先级:立即更新输入框

    startTransition(() => {
      // 低优先级:可以被中断
      const filtered = hugeDataset.filter(item =>
        item.name.includes(value)
      );
      setResults(filtered);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending && <Spinner />}
      <ul style={{ opacity: isPending ? 0.7 : 1 }}>
        {results.map(r => <li key={r.id}>{r.name}</li>)}
      </ul>
    </div>
  );
}

14.6 性能优化清单

类别 优化手段
渲染 React.memo, useMemo, useCallback, 状态下沉
加载 代码分割, lazy/Suspense, 预加载关键资源
列表 虚拟化(react-window/react-virtuoso), 稳定 key
网络 数据缓存(TanStack Query), 防抖/节流
打包 Tree shaking, 按需导入, 分析 bundle 大小
架构 Server Components, SSR/SSG, 流式渲染
工具 React DevTools Profiler, Lighthouse, Web Vitals

15. React 设计模式

15.1 容器/展示组件模式

jsx 复制代码
// 展示组件(纯 UI)
function UserCard({ user, onFollow }) {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <button onClick={() => onFollow(user.id)}>Follow</button>
    </div>
  );
}

// 容器组件(逻辑)
function UserCardContainer({ userId }) {
  const { data: user, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  const followMutation = useMutation({ mutationFn: followUser });

  if (isLoading) return <UserCardSkeleton />;
  return <UserCard user={user} onFollow={followMutation.mutate} />;
}

15.2 Compound Components(复合组件)

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

const TabsContext = createContext();

function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      className={activeTab === value ? 'active' : ''}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;
  return <div className="tab-panel">{children}</div>;
}

// 使用
function App() {
  return (
    <Tabs defaultTab="profile">
      <TabList>
        <Tab value="profile">Profile</Tab>
        <Tab value="settings">Settings</Tab>
        <Tab value="billing">Billing</Tab>
      </TabList>
      <TabPanel value="profile"><ProfilePage /></TabPanel>
      <TabPanel value="settings"><SettingsPage /></TabPanel>
      <TabPanel value="billing"><BillingPage /></TabPanel>
    </Tabs>
  );
}

15.3 Render Props

jsx 复制代码
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
      {render(position)}
    </div>
  );
}

// 使用
<MouseTracker
  render={({ x, y }) => (
    <p>Mouse: ({x}, {y})</p>
  )}
/>

注意:大多数 Render Props 场景现在可以用自定义 Hook 替代,更简洁。

15.4 高阶组件(HOC)

jsx 复制代码
function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const { user, loading } = useAuth();

    if (loading) return <Spinner />;
    if (!user) return <Navigate to="/login" />;

    return <WrappedComponent {...props} user={user} />;
  };
}

const ProtectedDashboard = withAuth(Dashboard);

注意:HOC 模式在 Hooks 时代使用减少,大多可用自定义 Hook 替代。

15.5 Error Boundary

jsx 复制代码
import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
    // 上报错误到监控平台
    reportError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 使用
function App() {
  return (
    <ErrorBoundary fallback={<AppErrorPage />}>
      <Header />
      <ErrorBoundary fallback={<ContentError />}>
        <MainContent />
      </ErrorBoundary>
      <Footer />
    </ErrorBoundary>
  );
}

16. TypeScript 与 React

16.1 基本类型定义

tsx 复制代码
// Props 类型
interface UserCardProps {
  name: string;
  age: number;
  avatar?: string;           // 可选
  role: 'admin' | 'user';    // 联合类型
  tags: string[];             // 数组
  onFollow: (id: string) => void;  // 函数
  children: React.ReactNode;  // React 子元素
}

function UserCard({ name, age, avatar, role, tags, onFollow, children }: UserCardProps) {
  return <div>{children}</div>;
}

// 带泛型的组件
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map(item => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 使用
<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => user.id}
/>

16.2 Hooks 类型

tsx 复制代码
// useState
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);

// useRef
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);

// useReducer
type Action =
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'SET'; payload: number };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    case 'DECREMENT': return state - 1;
    case 'SET': return action.payload;
  }
}

const [count, dispatch] = useReducer(reducer, 0);

// useContext
interface AuthContextType {
  user: User | null;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}

// 事件类型
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {};

17. 测试

17.1 测试工具

  • Vitest / Jest:测试运行器和断言库
  • React Testing Library:以用户视角测试组件
  • Playwright / Cypress:端到端测试
  • MSW (Mock Service Worker):API mock

17.2 React Testing Library 示例

jsx 复制代码
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';
import { LoginForm } from './LoginForm';

// 基本渲染测试
test('renders counter with initial value', () => {
  render(<Counter initialCount={5} />);
  expect(screen.getByText('Count: 5')).toBeInTheDocument();
});

// 用户交互测试
test('increments counter on button click', async () => {
  const user = userEvent.setup();
  render(<Counter initialCount={0} />);

  await user.click(screen.getByRole('button', { name: /increment/i }));
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

// 异步测试
test('submits login form', async () => {
  const mockLogin = jest.fn().mockResolvedValue({ success: true });
  const user = userEvent.setup();

  render(<LoginForm onSubmit={mockLogin} />);

  await user.type(screen.getByLabelText(/email/i), 'test@example.com');
  await user.type(screen.getByLabelText(/password/i), 'password123');
  await user.click(screen.getByRole('button', { name: /log in/i }));

  await waitFor(() => {
    expect(mockLogin).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
  });
});

// 自定义 Hook 测试
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(0));

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

17.3 测试最佳实践

  • 测试用户行为而非实现细节
  • 使用 getByRole, getByLabelText 等语义查询
  • 避免测试内部状态,测试渲染输出
  • 对关键路径编写端到端测试
  • 测试覆盖率目标 80% 以上

18. 服务端渲染(SSR)与 Next.js

18.1 渲染策略对比

策略 简称 说明 适用场景
客户端渲染 CSR 浏览器执行 JS 渲染 SPA、后台系统
服务端渲染 SSR 每次请求在服务端渲染 SEO 重要的动态页面
静态生成 SSG 构建时生成 HTML 博客、文档、营销页
增量静态再生成 ISR 定期在后台重新生成 电商、内容平台
流式 SSR Streaming 分块发送 HTML 首屏优化

18.2 Next.js App Router 基础

复制代码
app/
├── layout.tsx          # 根布局
├── page.tsx            # 首页 /
├── loading.tsx         # 加载状态
├── error.tsx           # 错误处理
├── not-found.tsx       # 404 页面
├── blog/
│   ├── page.tsx        # /blog
│   └── [slug]/
│       └── page.tsx    # /blog/:slug
└── api/
    └── users/
        └── route.ts    # API 路由 /api/users
tsx 复制代码
// app/layout.tsx - 根布局(Server Component)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

// app/blog/[slug]/page.tsx - 动态路由(Server Component)
interface PageProps {
  params: Promise<{ slug: string }>;
}

export default async function BlogPost({ params }: PageProps) {
  const { slug } = await params;
  const post = await fetchPost(slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// 生成静态路径(SSG)
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

// Server Actions
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  await db.posts.create({ title, content });
  revalidatePath('/blog');
}

19. React 生态工具链

19.1 构建工具

工具 说明
Vite 当前最流行的构建工具,极快的 HMR
Next.js 全栈 React 框架,SSR/SSG/App Router
Remix 全栈框架,专注 Web 标准
Create React App 已不推荐使用(已停止维护)

19.2 项目初始化

bash 复制代码
# Vite(推荐用于 SPA)
npm create vite@latest my-app -- --template react-ts

# Next.js(推荐用于全栈)
npx create-next-app@latest my-app --typescript --tailwind --app

# Remix
npx create-remix@latest my-app

19.3 常用生态库

类别 推荐库
状态管理 Zustand, Redux Toolkit, Jotai
数据获取 TanStack Query, SWR
路由 React Router, TanStack Router
表单 React Hook Form + Zod
UI 组件库 shadcn/ui, Ant Design, Material UI, Radix UI
动画 Framer Motion, React Spring
图表 Recharts, Visx, Nivo
国际化 react-i18next, next-intl
日期 date-fns, dayjs
拖拽 dnd-kit, @hello-pangea/dnd
表格 TanStack Table
虚拟列表 react-window, react-virtuoso
测试 Vitest, React Testing Library, Playwright
代码规范 ESLint, Prettier, Biome

20. 项目最佳实践

20.1 目录结构推荐

复制代码
src/
├── app/                    # 页面/路由(Next.js App Router)
├── components/             # 共享组件
│   ├── ui/                 # 基础 UI 组件(Button, Input, Modal...)
│   └── layout/             # 布局组件(Header, Footer, Sidebar...)
├── features/               # 按功能模块组织
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   └── types.ts
│   └── dashboard/
│       ├── components/
│       ├── hooks/
│       └── utils/
├── hooks/                  # 全局自定义 Hooks
├── lib/                    # 第三方库封装、工具函数
├── services/               # API 请求层
├── stores/                 # 全局状态(Zustand store)
├── types/                  # 全局 TypeScript 类型
├── utils/                  # 工具函数
└── styles/                 # 全局样式

20.2 代码规范

jsx 复制代码
// ✅ 组件文件命名:PascalCase
// UserProfile.tsx, UserCard.tsx

// ✅ Hook 文件命名:camelCase,以 use 开头
// useAuth.ts, useFetch.ts

// ✅ 工具函数:camelCase
// formatDate.ts, calculatePrice.ts

// ✅ 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = '/api/v1';

// ✅ 类型/接口:PascalCase
interface UserProfile {
  id: string;
  name: string;
}

// ✅ 组件 Props 接口命名
interface ButtonProps { /* ... */ }
interface UserCardProps { /* ... */ }

// ✅ 提前返回,减少嵌套
function UserProfile({ userId }) {
  const { data, isLoading, error } = useFetch(`/users/${userId}`);

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;
  if (!data) return null;

  return <div>{data.name}</div>; // 主逻辑
}

20.3 常见错误与避免

jsx 复制代码
// ❌ 错误 1: 直接修改状态
const [items, setItems] = useState([1, 2, 3]);
// items.push(4);        // ❌
setItems([...items, 4]); // ✅

// ❌ 错误 2: useEffect 缺少依赖
useEffect(() => {
  fetchData(userId); // userId 必须在依赖数组中
}, []); // ❌ 缺少 userId
// ✅ 正确:[userId]

// ❌ 错误 3: 在渲染中创建新对象/函数导致子组件重渲染
function Parent() {
  // ❌ 每次渲染都是新对象
  return <Child style={{ color: 'red' }} onClick={() => {}} />;
}
// ✅ 使用 useMemo/useCallback 或提取为常量

// ❌ 错误 4: 在 useEffect 中无限循环
const [data, setData] = useState([]);
useEffect(() => {
  setData([...data, newItem]); // data 变化 → 触发 effect → 又设置 data
}, [data]); // ❌ 无限循环
// ✅ 使用函数式更新:setData(prev => [...prev, newItem])

// ❌ 错误 5: 用 index 作为 key(列表会增删排序时)
{items.map((item, i) => <Item key={i} />)}  // ❌
{items.map(item => <Item key={item.id} />)} // ✅

// ❌ 错误 6: 过度使用 useEffect
// 很多场景(计算派生数据、事件处理、渲染逻辑)不需要 useEffect
// ✅ 派生数据直接在渲染中计算
const fullName = `${firstName} ${lastName}`; // 不需要 useEffect + useState

20.4 可访问性(Accessibility)

jsx 复制代码
// 使用语义化 HTML
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
  </ul>
</nav>

// 为图片添加 alt
<img src="photo.jpg" alt="A sunset over the mountains" />
<img src="decorative.jpg" alt="" /> {/* 装饰性图片用空 alt */}

// 表单关联 label
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint">We'll never share your email.</span>

// 按钮使用语义化标签
<button onClick={handleClose} aria-label="Close dialog">×</button>

// 管理焦点
const dialogRef = useRef();
useEffect(() => {
  dialogRef.current?.focus();
}, []);

附录:快速参考

Hooks 速查

Hook 用途 示例
useState 状态管理 const [x, setX] = useState(0)
useEffect 副作用 useEffect(() => { ... }, [dep])
useContext 消费 Context const val = useContext(MyCtx)
useReducer 复杂状态 const [s, d] = useReducer(fn, init)
useRef 引用/可变值 const ref = useRef(null)
useMemo 缓存计算 const v = useMemo(() => calc(), [dep])
useCallback 缓存函数 const fn = useCallback(() => {}, [dep])
useId 生成唯一 ID const id = useId()
useTransition 非紧急更新 const [p, start] = useTransition()
useDeferredValue 延迟值 const d = useDeferredValue(val)
useLayoutEffect 同步 DOM 副作用 类似 useEffect
useImperativeHandle 自定义 ref 暴露 配合 forwardRef
useSyncExternalStore 订阅外部 store 库作者使用
useActionState 表单 action 状态 React 19
useOptimistic 乐观更新 React 19
use 读取 Promise/Context React 19

React 19 速查

特性 说明
Actions async transitions 自动管理 pending/error
useActionState 表单 action 状态管理
useOptimistic 乐观 UI 更新
use() 渲染中读取 Promise/Context
React Compiler 自动 memoization(实验性)
Server Components 服务端渲染组件,零客户端 JS
Server Actions 直接从客户端调用服务端函数
ref as prop 函数组件直接接收 ref,无需 forwardRef
ref cleanup ref 回调可返回清理函数
文档元数据 组件内直接用 <title>, <meta>
资源预加载 prefetchDNS, preconnect, preload 等 API

持续学习资源

相关推荐
DanCheOo2 小时前
AI Streaming 架构:从浏览器到服务端的全链路流式设计
前端·agent
我是小趴菜2 小时前
前端如何让图片、视频、pdf等文件在浏览器直接下载而非预览
前端
cg332 小时前
开源项目自动化:用 GitHub Actions 让每个 Issue 都被温柔以待
前端
haierccc2 小时前
Win7、2008R2、Win10、Win11使用FLASH的方法
前端·javascript·html
We་ct2 小时前
LeetCode 50. Pow(x, n):从暴力法到快速幂的优化之路
开发语言·前端·javascript·算法·leetcode·typescript·
柠檬味的Cat2 小时前
使用腾讯云COS作为WordPress图床的实践
前端·github·腾讯云
Hilaku2 小时前
卷AI、卷算法、2026 年的前端工程师到底在卷什么?
前端·javascript·面试
非凡ghost2 小时前
AIMP(音乐播放软件)
前端·windows·音视频·firefox
xiaotao1312 小时前
Vite 完全学习指南
前端·vite·前端打包