React 常见问题与最佳实践总结

一、是什么

本文是 React 面试系列的收官篇,将 React 核心知识体系进行系统性梳理,涵盖项目结构、状态管理选型、性能优化清单、常见反模式、React 18/19 迁移要点及面试备战策略。目标是帮助你在面试中建立起清晰的知识框架。

二、项目结构最佳实践

按功能模块组织(Feature-based)

src/
  features/          # 按功能模块组织
    auth/            # 认证模块:components/ hooks/ api/ types.ts index.ts
    dashboard/       # 仪表盘模块
  shared/            # 跨模块共享:components/ hooks/ utils/ types/
  app/               # 应用入口:App.tsx routes.tsx providers.tsx

核心原则:

  • 按功能分目录而非按文件类型分目录
  • 共享代码统一放在 shared/ 目录
  • 每个功能模块通过 index.ts 明确导出公共 API
  • 组件与其专属 hook、测试文件就近放置

三、状态管理选型指南

是否需要跨组件共享状态?
├── 否 → useState / useReducer
└── 是 → 几个组件需要?
    ├── 少数相邻组件 → 状态提升 / 组合
    ├── 跨多层级 → useContext + useReducer
    └── 全局且复杂 → 外部状态库
        ├── 简单全局状态 → Zustand
        ├── 复杂业务逻辑 → Zustand / Jotai
        ├── 服务端状态 → TanStack Query / SWR
        └── 大型团队规范 → Redux Toolkit

各方案对比

方案适用场景学习成本Bundle 体积
useState组件内简单状态0
useReducer组件内复杂状态逻辑0
Context低频更新的全局数据(主题、语言)0
Zustand中小型应用全局状态~1KB
Jotai原子化状态、细粒度更新~2KB
Redux Toolkit大型应用、强调规范~11KB
TanStack Query服务端状态(接口缓存/同步)~12KB

关键原则:优先使用 React 内置方案,只有当内置方案不够用时才引入外部库。服务端状态(API 数据)和客户端状态(UI 状态)应分开管理。

四、性能优化清单

渲染优化

// 1. React.memo 避免无意义的重渲染
const ExpensiveList = React.memo(function ExpensiveList({ items }: Props) {
  return items.map((item) => <ListItem key={item.id} item={item} />);
});

// 2. useMemo 缓存昂贵计算
const sortedItems = useMemo(
  () => items.slice().sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// 3. useCallback 稳定回调引用
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// 4. 列表 key 使用稳定唯一标识,绝不用 index
{items.map((item) => <Item key={item.id} {...item} />)}

加载优化

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

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

// 6. 大列表使用 @tanstack/react-virtual 虚拟滚动

优化检查清单

  • 使用 React DevTools Profiler 找到不必要的重渲染
  • 大列表是否使用了虚拟滚动
  • 路由是否做了代码分割(lazy + Suspense)
  • 图片是否使用了 lazy loading 和适当格式(WebP/AVIF)
  • Context value 是否用 useMemo 包裹
  • 是否避免了在渲染函数中创建新对象/数组/函数
  • 频繁更新的状态是否与不频繁更新的状态分离
  • useEffect 依赖数组是否精确

五、常见反模式与解决方案

反模式一:在渲染中直接修改状态

// 错误:无限循环
function Counter() {
  const [count, setCount] = useState(0);
  setCount(count + 1); // 渲染期间调用 setState

  return <div>{count}</div>;
}

// 正确:在事件处理或 useEffect 中更新
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount((c) => c + 1)}>
      {count}
    </button>
  );
}

反模式二:useEffect 作为状态同步工具

// 错误:不必要的 effect 链
function Form({ items }) {
  const [filteredItems, setFilteredItems] = useState(items);
  const [query, setQuery] = useState('');

  useEffect(() => {
    setFilteredItems(items.filter((item) => item.name.includes(query)));
  }, [items, query]);

  return <List items={filteredItems} />;
}

// 正确:直接计算派生状态
function Form({ items }) {
  const [query, setQuery] = useState('');
  const filteredItems = useMemo(
    () => items.filter((item) => item.name.includes(query)),
    [items, query]
  );

  return <List items={filteredItems} />;
}

反模式三:过度使用 Context 导致性能问题

// 错误:一个巨大的 Context 包含所有状态
const AppContext = React.createContext({
  user: null,
  theme: 'light',
  notifications: [],
  sidebarOpen: false,
  // ... 更多状态
});

// 正确:按职责拆分 Context
const AuthContext = React.createContext(null);
const ThemeContext = React.createContext(null);
const UIContext = React.createContext(null);

反模式四:忘记清理副作用

// 错误:内存泄漏
useEffect(() => {
  const ws = new WebSocket('ws://api.example.com');
  ws.onmessage = (event) => setData(JSON.parse(event.data));
}, []);

// 正确:组件卸载时清理
useEffect(() => {
  const ws = new WebSocket('ws://api.example.com');
  ws.onmessage = (event) => setData(JSON.parse(event.data));
  return () => ws.close();
}, []);

反模式五:Props 透传地狱

// 错误:层层传递 props
<App user={user} theme={theme}>
  <Layout user={user} theme={theme}>
    <Sidebar user={user} theme={theme}>
      <UserMenu user={user} />

// 正确:使用 Context 或组合模式
function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <Layout>
          <Sidebar>
            <UserMenu />   {/* 内部通过 useAuth() 获取 user */}

六、React 18/19 迁移要点

React 18 核心变更

特性说明
并发渲染createRoot 启用,支持 startTransitionuseDeferredValue
自动批处理所有场景下 setState 自动批处理(包括 setTimeout、Promise)
Suspense 增强服务端流式渲染 + 选择性水合
useId生成服务端/客户端一致的唯一 ID
useSyncExternalStore安全订阅外部数据源

迁移步骤:

// 1. 替换 render 为 createRoot
// 旧
import { render } from 'react-dom';
render(<App />, document.getElementById('root'));

// 新
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);

// 2. 使用 startTransition 标记非紧急更新
import { startTransition } from 'react';

function handleSearch(query: string) {
  setInputValue(query);          // 紧急:更新输入框
  startTransition(() => {
    setSearchResults(query);     // 非紧急:可以延后
  });
}

React 19 新特性

特性说明
React Compiler自动 memo 优化,减少手动 useMemo/useCallback
ActionsuseActionState 简化表单提交与异步操作
use hook在渲染中读取 Promise 和 Context
改进的 refref 可作为普通 prop 传递,无需 forwardRef
文档元数据组件内直接渲染 <title><meta>
// React 19: use() hook 读取 Promise
function UserProfile({ userPromise }) {
  const user = use(userPromise);
  return <h1>{user.name}</h1>;
}

// React 19: ref 作为普通 prop,无需 forwardRef
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// React 19: useActionState 简化表单异步提交
const [state, formAction, isPending] = useActionState(loginAction, { error: null });

七、知识体系概览

React 知识体系
├── 基础:JSX / 虚拟 DOM / 组件 / Props & State / 生命周期 / 合成事件
├── 状态管理:useState / useReducer / Context / Zustand / Redux Toolkit / TanStack Query
├── Hooks:内置 Hooks / 自定义 Hooks / 闭包陷阱 / useMemo & useCallback
├── 性能:memo / key / 代码分割 / 虚拟滚动 / DevTools Profiler
├── 高级:Fiber / Diff / Error Boundary / 并发渲染 / RSC
├── 工程化:TypeScript / 测试 / 设计模式 / CSS 方案 / SSR & SSG
└── React 19:Compiler / Actions / use() / 简化 ref

八、面试备战策略

高频考点优先级

必须掌握(出现率 > 80%)

  • 虚拟 DOM 与 Diff 算法原理
  • Hooks 使用与原理(useState、useEffect、自定义 Hook)
  • 组件通信方式
  • 性能优化手段
  • 受控与非受控组件

重点掌握(出现率 50-80%)

  • Fiber 架构
  • 状态管理方案对比
  • React 18 新特性
  • 生命周期与 useEffect 对应关系
  • key 的作用与原理

进阶加分(出现率 < 50%)

  • React 19 新特性
  • React Server Components
  • 并发渲染深度
  • 设计模式运用
  • 测试策略

回答框架

面对 React 面试题,建议遵循这个结构:

  1. 是什么:一句话定义核心概念
  2. 为什么:解释这个特性解决了什么问题
  3. 怎么用:给出简洁的代码示例
  4. 深入:底层原理或实现机制
  5. 对比:与相关方案的对比(如有)
  6. 实践:在真实项目中的应用经验

关键心态

  • 不要死记硬背 API,理解设计动机更重要
  • 能讲清楚 "为什么这样设计" 比 "怎么用" 更有区分度
  • 准备 1-2 个自己项目中的真实案例,说明如何解决实际问题
  • 对不确定的知识坦诚说 "我了解大概原理但没有深入研究",比编造答案好得多