Skip to content

React 2026 面试题汇总

聚焦 React 19+、Server Components、React Compiler、Hooks 深度、并发特性与性能优化。已过时的类组件生命周期、HOC、Mixins 等不再收录。

目录


核心概念

1. React 的核心理念是什么?

React 的核心是 UI = f(state)

  • 声明式:描述 UI 应该是什么样子,而不是如何更新它
  • 组件化:将 UI 拆分为独立、可复用的组件
  • 单向数据流:数据从父组件流向子组件(props),子组件通过回调通知父组件
  • 虚拟 DOM:通过 diff 算法最小化真实 DOM 操作
jsx
// 声明式:描述"是什么",而非"怎么做"
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>点击了 {count} 次</button>;
}

2. JSX 的本质是什么?

JSX 是 React.createElement() 的语法糖。React 17+ 使用新的 JSX Transform,不再需要 import React

jsx
// JSX
const element = <h1 className="title">Hello</h1>;

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

关键点className 替代 class{}表达式、Fragment <>...</>、组件名大写开头。

3. 虚拟 DOM 和 Fiber 架构

虚拟 DOM:用 JS 对象描述真实 DOM,通过 diff 算法计算最小更新。

Fiber 架构(React 16+):将渲染工作拆分为多个小单元,每个单元执行完可以中断,优先处理高优先级更新(用户输入 > 数据请求),支持并发渲染。

typescript
// Fiber 节点简化结构
interface FiberNode {
  type: string | Function;      // 元素类型或组件函数
  key: string | null;
  child: FiberNode | null;       // 第一个子节点
  sibling: FiberNode | null;     // 下一个兄弟节点
  return: FiberNode | null;      // 父节点
  alternate: FiberNode | null;   // 双缓冲:当前树 ↔ 工作树
  flags: number;                 // 副作用标记
  lanes: number;                 // 优先级车道
}

Diff 策略:同层比较、类型不同直接替换子树、通过 key 优化列表操作。

4. 为什么列表渲染需要 key?

jsx
// ❌ index 作为 key:列表重排时状态错乱
{items.map((item, i) => <Item key={i} data={item} />)}

// ✅ 唯一 ID 作为 key
{items.map(item => <Item key={item.id} data={item} />)}

key 帮助 React 识别元素变化(新增/删除/移动)。用 index 会导致组件错误复用、输入框内容错位。

5. 合成事件

React 17+ 事件委托从 document 改为根容器,支持多 React 根共存。移除了事件池,onScroll 不再冒泡。


Hooks 深入

6. useState 的工作原理

jsx
function Counter() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(() => expensiveComputation()); // 惰性初始化

  const handleClick = () => {
    setCount(1); setCount(2); setCount(3);
    // React 18+ 自动批量处理,只触发一次重渲染
  };

  const increment = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    // count +2,函数式更新基于最新 prev
  };
}
  • React 18+ 所有场景自动批量处理(包括 setTimeout、Promise)
  • Object.is 判断新旧值,相同则跳过
  • 对象/数组必须返回新引用

7. useEffect 完全指南

jsx
useEffect(() => {
  let cancelled = false;
  async function fetchUser() {
    const res = await fetch(`/api/users/${userId}`);
    const data = await res.json();
    if (!cancelled) setUser(data);
  }
  fetchUser();
  return () => { cancelled = true; }; // 清理:防止竞态
}, [userId]);
写法含义
useEffect(fn)每次渲染后执行
useEffect(fn, [])仅挂载时执行
useEffect(fn, [a, b])a 或 b 变化时执行

闭包陷阱:effect 捕获旧值 → 用函数式更新 setCount(c => c+1)useRef 保存最新值。

8. useRef 的两种用途

jsx
const inputRef = useRef<HTMLInputElement>(null); // DOM 引用
const renderCount = useRef(0); // 可变值,修改不触发重渲染
renderCount.current += 1;

9. useMemo 与 useCallback

jsx
const filtered = useMemo(() => products.filter(p => p.category === category), [products, category]);
const handleSelect = useCallback((id: string) => { /* ... */ }, []);

2026 视角:React Compiler 出现后手动 memo 将不再必要;没有 Compiler 的项目中,仍需在传给 React.memo 子组件的 props、Hook 依赖、昂贵计算上使用。

10. useReducer 与复杂状态

jsx
type Action =
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; payload: Item[] }
  | { type: 'FETCH_ERROR'; payload: string };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'FETCH_START': return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS': return { ...state, loading: false, items: [...state.items, ...action.payload] };
    case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload };
  }
}

const [state, dispatch] = useReducer(reducer, initialState);
// dispatch 引用永远稳定,不需要 useCallback

选择:简单独立状态 → useState;多状态关联 → useReducer

11. 自定义 Hook

jsx
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();
    fetch(url, { signal: controller.signal })
      .then(res => res.json()).then(setData)
      .catch(err => { if (err.name !== 'AbortError') setError(err); })
      .finally(() => setLoading(false));
    return () => controller.abort();
  }, [url]);

  return { data, error, loading };
}

设计原则:use 开头命名、封装逻辑不封装 UI、每次调用状态独立。

12. Hooks 规则与原理

  1. 只在顶层调用——不在条件/循环/嵌套函数中
  2. 只在函数组件或自定义 Hook 中调用

原理:React 通过**调用顺序(链表)**匹配 Hook 和状态,顺序不一致则状态错乱。


React 19 新特性

13. Actions 与 useActionState

React 19 引入 Actions——处理表单提交和异步状态转换的标准模式。

jsx
import { useActionState } from 'react';

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

  return (
    <form action={submitAction}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      {state.error && <p className="error">{state.error}</p>}
      <button disabled={isPending}>{isPending ? '登录中...' : '登录'}</button>
    </form>
  );
}

核心变化:<form action={fn}> 接受函数、isPending 自动跟踪异步状态、支持渐进增强。

14. useOptimistic —— 乐观更新

jsx
import { useOptimistic } from 'react';

function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (current, newText) => [...current, { id: crypto.randomUUID(), text: newText, pending: true }]
  );

  async function handleSubmit(formData) {
    const text = formData.get('todo');
    addOptimisticTodo(text); // 立即更新 UI
    await addTodo(text);     // 请求完成后 todos prop 更新覆盖乐观值
  }

  return (
    <form action={handleSubmit}>
      <input name="todo" /><button type="submit">添加</button>
      <ul>
        {optimisticTodos.map(t => (
          <li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>
        ))}
      </ul>
    </form>
  );
}

15. use() Hook

use() 可在条件语句中调用(打破传统 Hooks 规则),用于读取 Promise 或 Context。

jsx
import { use, Suspense } from 'react';

function UserProfile({ userPromise }) {
  const user = use(userPromise); // 自动 suspend 直到 resolve
  return <h1>{user.name}</h1>;
}

function ThemeIcon({ show }) {
  if (show) {
    const theme = use(ThemeContext); // ✅ 条件中调用
    return <Icon color={theme.primary} />;
  }
  return null;
}

16. ref 作为 prop & ref 回调清理

jsx
// React 19:ref 直接作为 prop,告别 forwardRef
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// ref 回调清理函数(新增,类似 useEffect 清理)
<video ref={(node) => {
  if (node) node.play();
  return () => node?.pause();
}} />

17. 文档元数据与资源预加载

jsx
function BlogPost({ title, desc }) {
  return (
    <article>
      <title>{title}</title>
      <meta name="description" content={desc} />
      <link rel="stylesheet" href="theme.css" precedence="high" />
      {/* React 自动提升到 <head>、去重、按 precedence 排序 */}
      <h1>{title}</h1>
    </article>
  );
}

// 资源预加载
import { preload, preinit, prefetchDNS, preconnect } from 'react-dom';
preload('/fonts/inter.woff2', { as: 'font', type: 'font/woff2' });
preinit('/scripts/analytics.js', { as: 'script' });

Server Components

18. 什么是 React Server Components(RSC)?

Server Components 在服务器上渲染,代码不发送到客户端,可直接访问数据库/文件系统,零 bundle 体积。

jsx
// Server Component(默认,无需标记)
async function ProductPage({ id }) {
  const product = await db.products.findById(id);
  return (
    <div>
      <h1>{product.name}</h1>
      <AddToCartButton productId={id} price={product.price} />
    </div>
  );
}

// Client Component
'use client';
import { useState } from 'react';
function AddToCartButton({ productId, price }) {
  const [added, setAdded] = useState(false);
  return (
    <button onClick={() => { addToCart(productId); setAdded(true); }}>
      {added ? '✓ 已加入' : `¥${price} 加入购物车`}
    </button>
  );
}

19. Server Components vs SSR

特性SSRRSC
JS 发送到客户端全量 JSSC 代码不发送
水合(Hydration)全量水合只水合 Client Components
数据获取getServerSideProps 等组件内直接 await
流式渲染支持更细粒度

关键区别:SSR 仍需发送全部 JS 到客户端水合;RSC 的服务端组件代码永远不到客户端

20. 'use client' 和 'use server'

jsx
// 'use client' —— 标记客户端组件边界
'use client';
export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// 'use server' —— 标记 Server Action
'use server';
export async function createPost(formData: FormData) {
  await db.posts.create({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  revalidatePath('/posts');
}

21. Server Actions

Server Actions 是服务端执行的函数,客户端通过 RPC 调用:

jsx
// actions.ts
'use server';
export async function updateProfile(formData: FormData) {
  const session = await getSession();
  if (!session) throw new Error('Unauthorized');
  await db.users.update({
    where: { id: session.userId },
    data: { name: formData.get('name'), bio: formData.get('bio') },
  });
  revalidatePath('/profile');
}

// ProfileForm.tsx(Client Component)
'use client';
import { updateProfile } from './actions';
import { useActionState } from 'react';

function ProfileForm({ user }) {
  const [state, action, isPending] = useActionState(async (prev, formData) => {
    try { await updateProfile(formData); return { success: true }; }
    catch (e) { return { error: e.message }; }
  }, {});

  return (
    <form action={action}>
      <input name="name" defaultValue={user.name} />
      <textarea name="bio" defaultValue={user.bio} />
      <button disabled={isPending}>{isPending ? '保存中...' : '保存'}</button>
      {state.error && <p className="error">{state.error}</p>}
    </form>
  );
}

22. Server / Client Components 组合规则

Server → Server  ✅  可以直接导入
Server → Client  ✅  可以直接导入
Client → Server  ❌  不能直接导入
Client 通过 children/props 接收 Server Component  ✅
jsx
// ✅ 正确:Server Component 通过 children 传入 Client Component
// layout.tsx(Server Component)
import { Sidebar } from './Sidebar'; // Client
import { UserNav } from './UserNav'; // Server

export default function Layout({ children }) {
  return (
    <Sidebar>
      <UserNav />  {/* Server Component 作为 children */}
      {children}
    </Sidebar>
  );
}

// Sidebar.tsx
'use client';
export function Sidebar({ children }) {
  const [collapsed, setCollapsed] = useState(false);
  return (
    <aside className={collapsed ? 'w-16' : 'w-64'}>
      <button onClick={() => setCollapsed(!collapsed)}>Toggle</button>
      {children}
    </aside>
  );
}

React Compiler

23. React Compiler 是什么?

React Compiler(原名 React Forget)是编译时优化工具,自动将组件代码转换为记忆化版本,消除不必要的重渲染。

jsx
// 你写的代码(无需手动优化)
function ProductList({ products, category }) {
  const filtered = products.filter(p => p.category === category);
  const handleClick = (id) => { /* ... */ };
  return filtered.map(p => <ProductCard key={p.id} product={p} onClick={handleClick} />);
}

// Compiler 自动编译为等价的记忆化版本
// → filtered 只在 products/category 变化时重新计算
// → handleClick 引用稳定
// → ProductCard 不会因父组件无关状态变化而重渲染

核心原则

  • 假设代码遵循 React 规则(纯组件、不可变状态)
  • 不兼容的代码用 'use no memo' 跳过编译
  • 已在 Meta(Instagram)生产验证

24. React Compiler 对开发的影响

jsx
// 2026 之前:手动优化
const MemoChild = React.memo(Child);
const data = useMemo(() => ({ name: 'test' }), []);
const onClick = useCallback(() => {}, []);
<MemoChild data={data} onClick={onClick} />

// 2026(有 Compiler):直接写,编译器自动优化
const data = { name: 'test' };
const onClick = () => {};
<Child data={data} onClick={onClick} />

意味着 useMemouseCallbackReact.memo 在新项目中将逐步退出舞台。


并发特性

25. useTransition

将状态更新标记为非紧急,让 React 优先处理紧急更新(如输入框),保持 UI 响应。

jsx
import { useTransition, useState } from 'react';

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

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 紧急:立即更新输入框

    startTransition(() => {
      // 非紧急:可被中断,不阻塞输入
      setResults(searchDatabase(value));
    });
  };

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

26. useDeferredValue

延迟某个值的更新,适用于无法控制状态更新源的场景。

jsx
import { useDeferredValue, useMemo } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  const results = useMemo(() => heavyFilter(deferredQuery), [deferredQuery]);

  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      <ResultsList results={results} />
    </div>
  );
}

useTransition vs useDeferredValue

  • useTransition:你控制状态更新时使用,将 setState 包裹在 startTransition
  • useDeferredValue:你无法控制值的来源(如 props),对接收到的值做延迟

27. Suspense

jsx
import { Suspense, lazy } from 'react';

// 代码分割
const Dashboard = lazy(() => import('./Dashboard'));

// 数据获取(配合 use() 或 Server Components)
function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Dashboard />
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
    </Suspense>
  );
}

嵌套策略:外层捕获页面级加载、内层让不同区域独立加载。配合 useTransition 可在导航时保持旧页面,直到新页面就绪。


状态管理

28. 2026 年的状态管理选型

场景推荐方案
局部状态useState / useReducer
跨组件共享Context + useReducer / Zustand
服务端状态TanStack Query / SWR
URL 状态路由参数 + searchParams
表单状态React Hook Form / useActionState
全局复杂状态Zustand / Jotai / Redux Toolkit

29. Context 的正确用法与性能

jsx
// ✅ 拆分 Context 避免不必要重渲染
const UserContext = createContext<User | null>(null);
const ThemeContext = createContext<'light' | 'dark'>('light');

// ✅ 自定义 Hook 封装 + 空值保护
function useUser() {
  const user = useContext(UserContext);
  if (!user) throw new Error('useUser must be within UserProvider');
  return user;
}

// ✅ 拆分 state 和 dispatch
const StateCtx = createContext<State>(init);
const DispatchCtx = createContext<Dispatch<Action>>(() => {});

function Provider({ children }) {
  const [state, dispatch] = useReducer(reducer, init);
  return (
    <DispatchCtx.Provider value={dispatch}>
      <StateCtx.Provider value={state}>{children}</StateCtx.Provider>
    </DispatchCtx.Provider>
  );
}

性能问题:Context 值变化时所有消费者都重渲染,无论是否用到变化部分。解决:拆分 Context、useMemo 包裹 value、高频更新考虑 Zustand/Jotai。

30. Zustand

jsx
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set(s => ({ count: s.count + 1 })),
  fetchData: async () => {
    const data = await fetch('/api/data').then(r => r.json());
    set({ data });
  },
}));

// 选择器:组件只在订阅字段变化时重渲染
function Counter() {
  const count = useStore(s => s.count);
  const increment = useStore(s => s.increment);
  return <button onClick={increment}>{count}</button>;
}

为什么流行:无 Provider、极少样板代码、自动选择器优化、内置 persist/devtools 中间件。

31. TanStack Query(React Query)

服务端状态管理的事实标准:自动缓存、去重、后台刷新、错误重试。

jsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function UserList() {
  const { data, isLoading } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
    staleTime: 5 * 60 * 1000,
  });

  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: (newUser) => fetch('/api/users', {
      method: 'POST', body: JSON.stringify(newUser),
    }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
    // 乐观更新
    onMutate: async (newUser) => {
      await queryClient.cancelQueries({ queryKey: ['users'] });
      const prev = queryClient.getQueryData(['users']);
      queryClient.setQueryData(['users'], old => [...old, newUser]);
      return { prev };
    },
    onError: (err, _, ctx) => queryClient.setQueryData(['users'], ctx.prev),
  });
}

性能优化

32. 性能优化清单

jsx
// 1. 状态下放:把状态放到需要它的最近组件
// ❌
function App() {
  const [input, setInput] = useState('');
  return (<><SearchInput value={input} onChange={setInput} /><ExpensiveList /></>);
  // ExpensiveList 每次输入都重渲染
}
// ✅
function App() {
  return (<><SearchInput /><ExpensiveList /></>); // 状态在 SearchInput 内部
}

// 2. children 模式:把不变的部分通过 children 传入
function ColorPicker({ children }) {
  const [color, setColor] = useState('red');
  return (
    <div style={{ color }}>
      <input value={color} onChange={e => setColor(e.target.value)} />
      {children} {/* 引用不变,不重渲染 */}
    </div>
  );
}

// 3. 虚拟列表
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map(vi => (
          <div key={vi.key} style={{
            position: 'absolute',
            transform: `translateY(${vi.start}px)`,
            height: vi.size,
          }}>
            {items[vi.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

// 4. 代码分割
const Dashboard = lazy(() => import('./Dashboard'));

// 5. 图片优化:使用 next/image 或 loading="lazy"
<img src="photo.jpg" loading="lazy" decoding="async" />

33. 组件重渲染的触发条件

  1. 自身 state 变化
  2. 父组件重渲染(除非 React.memo / Compiler 优化)
  3. 消费的 Context 值变化

React DevTools Profiler 可录制渲染过程,查看每个组件耗时和重渲染原因。


TypeScript 与 React

34. 组件 Props 类型

tsx
// 基础 Props
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  children: React.ReactNode;
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

function Button({ variant, size = 'md', loading, children, onClick }: ButtonProps) {
  return (
    <button className={`btn-${variant} btn-${size}`} disabled={loading} onClick={onClick}>
      {loading ? <Spinner /> : children}
    </button>
  );
}

// 继承原生 HTML 属性
interface InputProps extends React.ComponentProps<'input'> {
  label: string;
  error?: string;
}

function Input({ label, error, ...rest }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...rest} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

35. 泛型组件

tsx
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

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

// 使用:类型自动推断
<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => user.id}
/>

36. 常用类型速查

tsx
// useRef
const inputRef = useRef<HTMLInputElement>(null);      // DOM
const countRef = useRef<number>(0);                   // 可变值
const timerRef = useRef<ReturnType<typeof setTimeout>>(null);

// 事件
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
  console.log(e.target.value);
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
  e.preventDefault();
};

// children
type Props = { children: React.ReactNode };           // 最宽泛
type Props2 = { children: React.ReactElement };       // 只接受 JSX 元素
type Props3 = { children: (data: T) => React.ReactNode }; // render props

// 组件类型
type FC = React.FC<Props>;                            // 不推荐(隐式 children)
type ComponentType = React.ComponentProps<typeof Button>; // 获取组件 Props 类型

测试

37. React 组件测试(Vitest + Testing Library)

tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';

describe('LoginForm', () => {
  it('提交表单时调用 onSubmit', async () => {
    const handleSubmit = vi.fn();
    const user = userEvent.setup();

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

    await user.type(screen.getByLabelText('邮箱'), 'test@example.com');
    await user.type(screen.getByLabelText('密码'), 'password123');
    await user.click(screen.getByRole('button', { name: '登录' }));

    expect(handleSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
  });

  it('显示验证错误', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={vi.fn()} />);
    await user.click(screen.getByRole('button', { name: '登录' }));
    expect(screen.getByText('请输入邮箱')).toBeInTheDocument();
  });
});

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

describe('useCounter', () => {
  it('递增计数', () => {
    const { result } = renderHook(() => useCounter(0));
    act(() => result.current.increment());
    expect(result.current.count).toBe(1);
  });
});

测试原则

  • 按用户行为测试(点击、输入、提交),而非实现细节
  • 优先使用 getByRolegetByLabelText,而非 getByTestId
  • 异步操作用 waitForfindByText
  • Mock 网络请求用 MSW(Mock Service Worker)

架构与设计模式

38. 组合组件(Compound Components)

jsx
function Tabs({ children, defaultValue }) {
  const [active, setActive] = useState(defaultValue);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      {children}
    </TabsContext.Provider>
  );
}

Tabs.List = ({ children }) => <div role="tablist">{children}</div>;

Tabs.Tab = ({ value, children }) => {
  const { active, setActive } = useContext(TabsContext);
  return (
    <button role="tab" aria-selected={active === value} onClick={() => setActive(value)}>
      {children}
    </button>
  );
};

Tabs.Panel = ({ value, children }) => {
  const { active } = useContext(TabsContext);
  return active === value ? <div role="tabpanel">{children}</div> : null;
};

// 使用:灵活组合
<Tabs defaultValue="tab1">
  <Tabs.List>
    <Tabs.Tab value="tab1">标签1</Tabs.Tab>
    <Tabs.Tab value="tab2">标签2</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="tab1">内容1</Tabs.Panel>
  <Tabs.Panel value="tab2">内容2</Tabs.Panel>
</Tabs>

39. Container / Presentation 分离(Hook 版)

jsx
// Container(逻辑 Hook)
function useProductList() {
  const { data, isLoading } = useQuery({ queryKey: ['products'], queryFn: fetchProducts });
  const [sortBy, setSortBy] = useState('name');
  const sorted = useMemo(() => sortProducts(data, sortBy), [data, sortBy]);
  return { products: sorted, isLoading, sortBy, setSortBy };
}

// Presentation(纯 UI)
function ProductList() {
  const { products, isLoading, sortBy, setSortBy } = useProductList();
  if (isLoading) return <Skeleton />;
  return (/* 纯展示 */);
}

40. 错误边界

jsx
// 错误边界仍需类组件(React 19 未提供 Hook 版本)
// 推荐使用 react-error-boundary 库
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary
      fallbackRender={({ error, resetErrorBoundary }) => (
        <div>
          <p>出错了: {error.message}</p>
          <button onClick={resetErrorBoundary}>重试</button>
        </div>
      )}
      onReset={() => queryClient.clear()}
    >
      <MainContent />
    </ErrorBoundary>
  );
}

注意:错误边界不能捕获事件处理器中的错误、异步代码错误、服务端渲染错误——这些需要 try/catch


进阶深入

41. React 渲染流程

触发更新

Render 阶段(可中断)
  ├── 调用组件函数,生成新 React Element
  ├── Diff:对比新旧 Fiber 树
  └── 标记变更节点(effectTag)

Commit 阶段(不可中断)
  ├── Before Mutation(DOM 快照)
  ├── Mutation(执行 DOM 操作)
  └── Layout(useLayoutEffect、更新 ref)

Passive Effects(异步)
  └── 执行 useEffect

42. 闭包陷阱与解决方案

jsx
function Timer() {
  const [count, setCount] = useState(0);

  // ❌ 闭包陷阱:count 被捕获,永远是 0
  useEffect(() => {
    const id = setInterval(() => setCount(count + 1), 1000); // 永远设为 1
    return () => clearInterval(id);
  }, []);

  // ✅ 方案1:函数式更新
  useEffect(() => {
    const id = setInterval(() => setCount(c => c + 1), 1000);
    return () => clearInterval(id);
  }, []);

  // ✅ 方案2:useRef 保存最新值
  const countRef = useRef(count);
  useEffect(() => { countRef.current = count; });
  useEffect(() => {
    const id = setInterval(() => console.log(countRef.current), 1000);
    return () => clearInterval(id);
  }, []);
}

43. 从 React 18 迁移到 19

已移除/废弃

  • forwardRef → ref 直接作为 prop
  • propTypes / defaultProps → TypeScript 替代
  • string refs / findDOMNodeuseRef / callback ref

新增

  • useActionState — Action 状态管理
  • useOptimistic — 乐观更新
  • use() — 读取 Promise / Context
  • ref 回调清理函数
  • 文档元数据(<title><meta>
  • 资源预加载(preloadpreinit

44. React 与 Web Components 互操作

jsx
// React 19 改善了与 Web Components 的互操作性
function App() {
  return (
    <my-datepicker
      value="2026-03-25"
      onchange={(e) => console.log(e.detail)}
      min-date="2026-01-01"
    />
  );
}
// 原生事件名直接传递,自定义属性正确映射

总结

2026 React 面试核心知识图谱:

基础层
├── JSX 本质与新 Transform
├── 虚拟 DOM + Fiber 架构 + Diff 策略
├── 函数组件(类组件仅错误边界)
└── 合成事件(根容器委托)

Hooks 层
├── useState / useReducer(状态)
├── useEffect / useLayoutEffect(副作用)
├── useRef(DOM引用 / 可变值)
├── useMemo / useCallback(→ 被 Compiler 替代)
├── useContext(上下文)
└── 自定义 Hook(逻辑复用)

React 19
├── useActionState(表单 Actions)
├── useOptimistic(乐观更新)
├── use()(读取 Promise/Context)
├── ref 作为 prop(告别 forwardRef)
├── 文档元数据 + 资源预加载
└── Server Components + Server Actions

并发特性
├── useTransition(非紧急更新)
├── useDeferredValue(延迟值)
└── Suspense(异步边界 + 流式渲染)

生态
├── 状态管理:Zustand / Jotai / TanStack Query
├── 路由:React Router v7 / TanStack Router
├── 测试:Vitest + Testing Library + MSW
└── 构建:React Compiler(自动记忆化)

面试建议:React 19 的 Actions、Server Components、React Compiler 是 2026 年的新热点。同时 Hooks 深度理解(闭包陷阱、自定义 Hook 设计)、并发特性(useTransition / Suspense)、状态管理选型仍是高频考点。准备时结合实际项目经验讲解最有说服力。