React 2026 面试题汇总
聚焦 React 19+、Server Components、React Compiler、Hooks 深度、并发特性与性能优化。已过时的类组件生命周期、HOC、Mixins 等不再收录。
目录
核心概念
1. React 的核心理念是什么?
React 的核心是 UI = f(state):
- 声明式:描述 UI 应该是什么样子,而不是如何更新它
- 组件化:将 UI 拆分为独立、可复用的组件
- 单向数据流:数据从父组件流向子组件(props),子组件通过回调通知父组件
- 虚拟 DOM:通过 diff 算法最小化真实 DOM 操作
// 声明式:描述"是什么",而非"怎么做"
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
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+):将渲染工作拆分为多个小单元,每个单元执行完可以中断,优先处理高优先级更新(用户输入 > 数据请求),支持并发渲染。
// 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?
// ❌ 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 的工作原理
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 完全指南
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 的两种用途
const inputRef = useRef<HTMLInputElement>(null); // DOM 引用
const renderCount = useRef(0); // 可变值,修改不触发重渲染
renderCount.current += 1;9. useMemo 与 useCallback
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 与复杂状态
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
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 规则与原理
- 只在顶层调用——不在条件/循环/嵌套函数中
- 只在函数组件或自定义 Hook 中调用
原理:React 通过**调用顺序(链表)**匹配 Hook 和状态,顺序不一致则状态错乱。
React 19 新特性
13. Actions 与 useActionState
React 19 引入 Actions——处理表单提交和异步状态转换的标准模式。
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 —— 乐观更新
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。
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 回调清理
// 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. 文档元数据与资源预加载
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 体积。
// 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
| 特性 | SSR | RSC |
|---|---|---|
| JS 发送到客户端 | 全量 JS | SC 代码不发送 |
| 水合(Hydration) | 全量水合 | 只水合 Client Components |
| 数据获取 | getServerSideProps 等 | 组件内直接 await |
| 流式渲染 | 支持 | 更细粒度 |
关键区别:SSR 仍需发送全部 JS 到客户端水合;RSC 的服务端组件代码永远不到客户端。
20. 'use client' 和 'use server'
// '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 调用:
// 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 ✅// ✅ 正确: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)是编译时优化工具,自动将组件代码转换为记忆化版本,消除不必要的重渲染。
// 你写的代码(无需手动优化)
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 对开发的影响
// 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} />意味着 useMemo、useCallback、React.memo 在新项目中将逐步退出舞台。
并发特性
25. useTransition
将状态更新标记为非紧急,让 React 优先处理紧急更新(如输入框),保持 UI 响应。
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
延迟某个值的更新,适用于无法控制状态更新源的场景。
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
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 的正确用法与性能
// ✅ 拆分 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
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)
服务端状态管理的事实标准:自动缓存、去重、后台刷新、错误重试。
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. 性能优化清单
// 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. 组件重渲染的触发条件
- 自身
state变化 - 父组件重渲染(除非
React.memo/ Compiler 优化) - 消费的
Context值变化
React DevTools Profiler 可录制渲染过程,查看每个组件耗时和重渲染原因。
TypeScript 与 React
34. 组件 Props 类型
// 基础 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. 泛型组件
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. 常用类型速查
// 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)
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);
});
});测试原则:
- 按用户行为测试(点击、输入、提交),而非实现细节
- 优先使用
getByRole、getByLabelText,而非getByTestId - 异步操作用
waitFor或findByText - Mock 网络请求用 MSW(Mock Service Worker)
架构与设计模式
38. 组合组件(Compound Components)
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 版)
// 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. 错误边界
// 错误边界仍需类组件(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(异步)
└── 执行 useEffect42. 闭包陷阱与解决方案
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 直接作为 proppropTypes/defaultProps→ TypeScript 替代string refs/findDOMNode→useRef/ callback ref
新增:
useActionState— Action 状态管理useOptimistic— 乐观更新use()— 读取 Promise / Context- ref 回调清理函数
- 文档元数据(
<title>、<meta>) - 资源预加载(
preload、preinit)
44. React 与 Web Components 互操作
// 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)、状态管理选型仍是高频考点。准备时结合实际项目经验讲解最有说服力。