React 前端开发 - 面试题库


一、基础题 ⭐

Q1. React 中 useState 的工作原理是什么?

答案useState 是 React Hooks 提供的状态管理函数,调用时返回当前状态和更新函数。React 内部通过 Fiber 节点存储组件状态,每次渲染创建一个新的 Hook 节点并链接到链表。状态更新函数调用后,React 标记组件为脏节点,在下次渲染时对比新旧状态决定是否重新渲染。状态更新是异步的,React 18 支持自动批处理多个状态更新。 关联知识点:Hooks 原理、Fiber 架构、批处理更新

Q2. useEffect 的依赖数组有什么作用?

答案:依赖数组控制 useEffect 的执行时机。空数组 [] 表示仅在组件挂载和卸载时执行一次;包含变量时表示在挂载和依赖变化时执行;无数组时表示每次渲染后都执行。清理函数在下次执行前或组件卸载时调用,用于取消订阅、清除定时器等。依赖数组应包含 effect 中使用的所有响应式值,避免闭包陷阱。 关联知识点:副作用管理、生命周期、闭包陷阱

Q3. React 中 key 的作用是什么?为什么不能用 index?

答案key 帮助 React 识别列表项的身份,在 Diff 算法中判断哪些元素被添加、删除或重新排序。使用稳定唯一的 key 可以避免不必要的重新渲染和状态错乱。使用 index 作为 key 的问题:列表项顺序变化时会导致错误的组件复用;插入或删除项时会引起不必要的重新渲染;组件内部状态(如输入框内容)可能错乱。应使用数据的唯一 ID 作为 key。 关联知识点:Diff 算法、虚拟 DOM、性能优化

Q4. React.memo 是什么?与 useMemo 有什么区别?

答案React.memo 是高阶组件,用于缓存整个组件,当 props 未变化时跳过重新渲染。useMemo 是 Hook,用于缓存计算结果,避免每次渲染都执行昂贵操作。React.memo 作用于组件级别,useMemo 作用于值级别。两者都使用浅比较判断变化,都可自定义比较函数。React.memo 适合纯展示组件优化,useMemo 适合昂贵计算缓存。 关联知识点:性能优化、组件缓存、计算缓存

Q5. React 中如何提升状态(Lift State Up)?

答案:当多个组件需要共享状态时,将状态提升到它们最近的共同父组件中。子组件通过 props 接收状态和更新函数,父组件统一管理状态。这遵循 React 的单向数据流原则,避免状态不一致。对于深层嵌套,可使用 Context API 或状态管理库避免 prop drilling。提升状态的原则:状态应放在需要它的最底层组件的最近公共祖先中。 关联知识点:状态管理、单向数据流、组件设计

Q6. React Router 中 <Outlet /> 的作用是什么?

答案<Outlet /> 是 React Router v6 引入的组件,用于在嵌套路由中渲染子路由。父路由组件使用 <Outlet /> 作为占位符,匹配的子路由会渲染在该位置。这实现了布局组件的复用,如 Dashboard 布局中侧边栏固定,主内容区域根据路由变化。配合 <Routes> 和嵌套 <Route> 使用,支持多层嵌套。 关联知识点:嵌套路由、布局组件、路由复用

Q7. React 中如何处理表单(受控组件与非受控组件)?

答案:受控组件:表单数据由 React 状态管理,输入框的 value 绑定状态,onChange 更新状态,数据流可控但性能略低。非受控组件:表单数据由 DOM 自身管理,使用 useRef 访问 DOM 节点获取值,适合简单表单或与第三方库集成。推荐使用受控组件处理复杂表单(验证、动态字段),非受控组件处理简单场景。 关联知识点:表单处理、受控组件、useRef

Q8. 什么是 React 的合成事件(SyntheticEvent)?

答案:合成事件是 React 对原生 DOM 事件的跨浏览器包装,提供一致的接口。React 使用事件委托机制,在根节点统一监听,事件触发时创建合成事件并冒泡到目标组件。React 17 之后事件委托绑定在 React 根节点而非 document,便于与微前端集成。合成事件池化机制在 React 17 已移除,现在可以异步访问事件属性。 关联知识点:事件系统、事件委托、跨浏览器兼容


二、进阶题 ⭐⭐

Q9. React 中 useCallback 和 useMemo 的使用场景和区别?

答案useCallback(fn, deps) 缓存函数引用,返回记忆化的函数,仅在依赖变化时重新创建,主要用于传递给子组件避免不必要的重新渲染。useMemo(fn, deps) 缓存计算结果,仅在依赖变化时重新计算,用于避免昂贵的重复计算。两者都用于性能优化,但 useCallback 针对函数,useMemo 针对值。不应过度使用,应先测量性能瓶颈。 关联知识点:性能优化、函数记忆、值记忆

Q10. React Context API 的使用场景和性能问题是什么?

答案:Context 用于在组件树中共享数据,避免逐层传递 props。适合传递全局数据(主题、语言、认证)。性能问题:Context 值变化时,所有订阅的组件都会重新渲染,即使组件只使用值的一部分。优化方案:拆分多个 Context、使用 useMemo 缓存值、将消费者组件拆分以减少渲染范围。频繁变化的状态应使用状态管理库而非 Context。 关联知识点:状态共享、性能优化、组件渲染

Q11. 什么是 React 的自定义 Hook?如何编写?

答案:自定义 Hook 是封装可复用状态逻辑的函数,以 use 开头,内部可调用其他 Hooks。它将组件中的状态逻辑提取为独立单元,实现关注点分离。每次调用都创建独立的状态实例,不会共享。适合封装数据获取、表单处理、窗口尺寸监听、本地存储同步等逻辑。命名必须以 use 开头,以便 React 检测 Hooks 调用规则。

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    let cancelled = false;
    fetch(url).then(r => r.json()).then(d => { if (!cancelled) { setData(d); setLoading(false); } });
    return () => { cancelled = true; };
  }, [url]);
  return { data, loading };
}

关联知识点:逻辑复用、组合模式、关注点分离

Q12. Redux Toolkit 相比原生 Redux 有什么改进?

答案:Redux Toolkit(RTK)简化了 Redux 配置,内置 Immer 实现不可变更新,无需展开操作符。createSlice 自动生成 action creators 和 reducers,减少样板代码。configureStore 内置 Redux DevTools 和常用中间件。RTK Query 提供声明式数据获取和缓存,替代手写 thunk。RTK 是官方推荐的 Redux 使用方式,大幅降低学习曲线和代码量。 关联知识点:状态管理、不可变数据、Redux

Q13. React 中如何实现路由守卫?

答案:路由守卫通过包装组件或自定义 Hook 实现,在渲染前检查条件(如认证状态)。未满足条件时重定向到登录页或显示拒绝访问。实现方式:创建 <ProtectedRoute> 组件,内部检查认证状态,已认证则渲染 <Outlet /> 或子组件,未认证则使用 <Navigate to="/login" replace /> 重定向。可扩展支持权限检查、数据预加载等。

const ProtectedRoute = ({ children }) => {
  const isAuthenticated = useAuth();
  return isAuthenticated ? children : <Navigate to="/login" replace />;
};

关联知识点:路由控制、权限验证、条件渲染

Q14. React 中的错误边界(Error Boundary)是什么?

答案:错误边界是捕获子组件树中 JavaScript 错误的 React 组件,防止整个应用崩溃。通过实现 static getDerivedStateFromError()componentDidCatch() 生命周期方法捕获错误。目前仅支持类组件,函数组件无法作为错误边界。错误边界捕获渲染期、生命周期和构造函数中的错误,不捕获事件处理、异步代码和 SSR 错误。推荐在应用顶层和关键模块使用。 关联知识点:错误处理、容错设计、类组件

Q15. React 中 forwardRef 和 useImperativeHandle 的用途是什么?

答案forwardRef 允许父组件访问子组件的 DOM 节点或实例,通过 ref 转发将 ref 传递给子组件内部元素。useImperativeHandleforwardRef 配合,自定义暴露给父组件的实例值,避免暴露内部实现。适用场景:表单自动聚焦、滚动到指定位置、调用子组件方法。应谨慎使用,优先使用 props 和数据流。

const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  const internalRef = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => ({
    focus: () => internalRef.current?.focus(),
    clear: () => { internalRef.current.value = ''; }
  }));
  return <input ref={internalRef} {...props} />;
});

关联知识点:Ref 转发、组件抽象、命令式 API


三、高级题 ⭐⭐⭐

Q16. React Fiber 架构的原理是什么?解决了什么问题?

答案:Fiber 是 React 16 重新实现的协调引擎,将递归的渲染过程转换为可中断的循环。Fiber 节点代表组件实例,以链表结构组织,支持优先级调度和时间切片。两阶段提交:Render 阶段(可中断,计算变更)和 Commit 阶段(不可中断,应用 DOM 更新)。解决了长任务阻塞主线程的问题,支持并发渲染、Suspense、优先级调度等特性。 关联知识点:并发渲染、时间切片、任务调度

Q17. React 18 的并发特性(Concurrent Features)有哪些?

答案startTransition 将更新标记为非紧急,允许紧急更新(如输入)插队,避免阻塞用户交互。useDeferredValue 延迟更新某个值,类似防抖但由 React 控制时机。useTransition Hook 返回 isPending 状态和 startTransition 函数。Suspense 支持服务端渲染和并发模式。并发渲染基于 Fiber 的时间切片,在浏览器空闲时执行更新,提高响应性。 关联知识点:并发渲染、优先级调度、用户体验

Q18. React 中如何实现高性能的虚拟列表?

答案:虚拟列表仅渲染可视区域内的项,大幅减少 DOM 节点。实现原理:计算可视区域高度和每项高度,通过 transformpadding 占位未渲染的项。监听滚动事件,计算可见项的起始和结束索引。动态高度需测量每项实际高度或使用估算值。第三方库:react-windowreact-virtualized。优化点:使用 shallow 比较、固定高度、窗口滚动节流。

const VirtualList = ({ items, height, itemHeight }) => {
  const [scrollTop, setScrollTop] = useState(0);
  const visibleStart = Math.floor(scrollTop / itemHeight);
  const visibleEnd = Math.min(visibleStart + Math.ceil(height / itemHeight), items.length);
  const offsetY = visibleStart * itemHeight;
  
  return (
    <div style={{ height, overflow: 'auto' }} onScroll={e => setScrollTop(e.target.scrollTop)}>
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {items.slice(visibleStart, visibleEnd).map((item, i) => (
          <div key={i} style={{ position: 'absolute', top: offsetY + i * itemHeight }}>{item}</div>
        ))}
      </div>
    </div>
  );
};

关联知识点:DOM 优化、滚动处理、性能优化

Q19. React Server Components(RSC)的原理和优势是什么?

答案:RSC 是 Next.js App Router 和 React 19 引入的服务端组件,在服务端渲染,不发送到客户端。优势:减少客户端包体积、直接访问后端资源(数据库、文件系统)、支持流式渲染。RSC 可以使用 async/await,不能使用状态和事件。客户端组件使用 'use client' 标记。RSC 与客户端组件混合使用,通过 props 传递数据。序列化时仅传递可序列化的数据,不传递函数。 关联知识点:SSR、全栈开发、性能优化

Q20. React 中如何处理大规模状态管理?

答案:拆分状态为多个 slice 或 atom,避免单一巨大 store。使用状态管理库的选择:Redux 适合大型复杂应用,状态变化可追踪;Zustand 轻量,无需 Provider;Jotai 原子化,粒度更细。状态应靠近使用它的组件,避免全局状态过多。使用选择器函数订阅状态片段,避免不必要的重新渲染。服务端状态使用 React Query/SWR,客户端状态使用 Redux/Zustand。 关联知识点:状态管理、架构设计、性能优化


四、实战场景题 🛠️

Q21. 如何实现一个防抖搜索输入框?

答案:使用自定义 Hook 封装防抖逻辑,内部使用 useEffect 设置定时器,依赖变化时清除旧定时器。结合 React Query 实现搜索请求的缓存和去重。输入框使用受控组件,onChange 更新搜索词,防抖后触发查询。显示加载状态和错误信息。使用 useDeferredValue 也可以实现类似效果,由 React 控制更新时机。

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debouncedValue;
}

const SearchBox = () => {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);
  const { data } = useSearchQuery(debouncedQuery);
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
};

关联知识点:防抖、自定义 Hook、数据获取

Q22. 如何实现一个权限控制的组件和 Hook?

答案:创建 usePermission Hook 检查用户权限,返回布尔值。创建 <Permission> 组件,根据权限决定是否渲染子组件,支持 fallback 显示无权限提示。高阶组件 withPermission 包装需要权限的组件。权限数据从全局状态获取,支持多权限组合(与/或关系)。

const Permission: React.FC<{ required: string[]; type?: 'all' | 'any'; fallback?: React.ReactNode }> = ({
  required, type = 'all', fallback = null, children
}) => {
  const userPerms = useUserStore(state => state.permissions);
  const hasAccess = type === 'all' ? required.every(p => userPerms.includes(p)) : required.some(p => userPerms.includes(p));
  return hasAccess ? <>{children}</> : <>{fallback}</>;
};

关联知识点:权限控制、组件设计、状态管理

Q23. 如何实现一个支持撤销/重做的编辑器?

答案:使用命令模式将操作封装为对象,包含 executeundo 方法。维护历史栈数组,执行操作时推入栈并清除当前索引之后的历史记录。撤销操作回退索引并调用 undo,重做操作前进索引并调用 execute。状态使用 useReducer 管理,支持最大历史记录数限制。

interface Command { execute: () => void; undo: () => void; }

function useHistory() {
  const [history, setHistory] = useState<Command[]>([]);
  const [index, setIndex] = useState(-1);
  
  const execute = (cmd: Command) => {
    cmd.execute();
    setHistory(prev => [...prev.slice(0, index + 1), cmd]);
    setIndex(prev => prev + 1);
  };
  
  const undo = () => { if (index >= 0) { history[index].undo(); setIndex(index - 1); } };
  const redo = () => { if (index < history.length - 1) { setIndex(index + 1); history[index + 1].execute(); } };
  
  return { execute, undo, redo, canUndo: index >= 0, canRedo: index < history.length - 1 };
}

关联知识点:命令模式、状态管理、撤销/重做

Q24. 如何实现一个表单生成器(根据 JSON Schema 生成表单)?

答案:定义 JSON Schema 描述表单结构(字段类型、验证规则、布局),使用递归组件渲染表单。根据字段类型动态渲染对应输入组件(文本、数字、选择、日期)。使用工厂模式创建不同类型的表单字段。验证规则使用策略模式,根据规则类型执行对应验证函数。表单状态使用 useReducer 或状态管理库管理。

const FormField: React.FC<{ schema: FieldSchema; value: any; onChange: (val: any) => void }> = ({ schema, value, onChange }) => {
  switch (schema.type) {
    case 'text': return <input type="text" value={value} onChange={e => onChange(e.target.value)} />;
    case 'select': return <select value={value} onChange={e => onChange(e.target.value)}>
      {schema.options?.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
    </select>;
    default: return null;
  }
};

关联知识点:工厂模式、策略模式、动态渲染

Q25. 如何在 React 中集成和使用 TypeScript?

答案:使用 React.FC<Props> 或函数签名定义组件类型,Props 接口定义属性类型。事件处理使用 React.ChangeEvent<HTMLInputElement>React.FormEvent 等类型。Ref 使用 useRef<HTMLDivElement>(null) 指定元素类型。泛型组件提高复用性(如 <T>(props: ListProps<T>))。Hooks 类型自动推导,复杂情况使用类型断言。使用 @types/react@types/react-dom 提供类型定义。

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'sm' | 'md' | 'lg';
  onClick: () => void;
  children: React.ReactNode;
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({ variant, size, onClick, children, disabled = false }) => (
  <button className={`btn-${variant}-${size}`} onClick={onClick} disabled={disabled}>{children}</button>
);

关联知识点:TypeScript、类型系统、泛型


合计:25 道题目,涵盖 React 基础、进阶、高级和实战场景