React 是什么?

  • React 是一个用于构建用户界面的 JavaScript 库,核心是组件化、声明式 UI 和状态驱动视图。
  • React 只关心视图层,本身不强制路由、请求、状态管理方案。
  • 它适合把复杂页面拆成多个可复用组件,再通过数据变化驱动页面更新。

React 的核心思想有哪些?

  • 组件化:把页面拆成独立组件,降低维护成本。
  • 声明式:描述 UI 的最终状态,不直接命令式操作 DOM。
  • 单向数据流:父组件通过 props 向下传递数据,子组件通过回调通知父组件。
  • 状态驱动视图:state 或 props 变化后,React 重新计算 UI。

React 和 Vue 的区别?

  • React 更偏函数和 JSX 表达能力,Vue 更偏模板和渐进式框架体验。
  • React 官方生态相对克制,路由、状态、请求多由社区方案组合。
  • Vue 提供更多内置约定,比如模板语法、指令、SFC、响应式系统等。
  • 面试时不要说谁更好,重点说团队技术栈、项目复杂度和生态选择。

JSX 是什么?

  • JSX 是 JavaScript 的语法扩展,可以在 JS 中写类似 HTML 的结构。
  • JSX 最终会被编译成 React 元素创建逻辑,不是浏览器原生语法。
  • JSX 可以直接使用变量、表达式和函数,因此组合能力很强。

JSX 中为什么要用 className?

  • class 是 JavaScript 保留字,React 使用 className 映射 HTML 的 class 属性。
  • 类似的还有 htmlFor,对应 label 的 for 属性。

React 元素和 React 组件有什么区别?

  • React 元素是描述 UI 的普通对象,比如 <div /> 的结果。
  • React 组件是返回 React 元素的函数或类。
  • 组件可以接收 props、维护状态、组合其他组件。

props 和 state 的区别?

  • props 是父组件传入的数据,组件内部不应该直接修改。
  • state 是组件自己的状态,可以通过 setState 或 Hook 更新。
  • props 更适合外部输入,state 更适合内部交互状态。

为什么不能直接修改 state?

  • 直接修改对象不会改变引用,React 可能无法判断数据已经变化。
  • 直接修改会破坏状态可预测性,调试和回溯都更困难。
  • 正确方式是创建新对象或新数组,再调用 state 更新函数。

什么是单向数据流?

  • 数据通常从父组件流向子组件。
  • 子组件不能直接修改父组件数据,只能通过回调通知父组件更新。
  • 好处是数据来源清晰,问题更容易定位。

为什么 React 组件名必须大写?

  • 小写标签会被 React 当成原生 DOM 标签。
  • 大写开头会被识别为自定义组件。
  • 例如 <button /> 是 DOM 标签,<Button /> 是组件。

key 的作用是什么?

  • key 用于帮助 React 识别列表中每一项的稳定身份。
  • 当列表插入、删除、排序时,稳定 key 能减少错误复用和不必要更新。
  • key 只在同层兄弟节点之间需要唯一,不要求全局唯一。

为什么不推荐用 index 做 key?

  • 列表顺序变化时,index 会跟着变化,组件状态可能错位。
  • 比如输入框列表用 index 做 key,删除一项后可能出现内容串位。
  • 只有静态列表、不排序、不增删时,index 才相对安全。

什么是受控组件?

  • 表单值由 React state 控制。
  • 输入变化时通过 onChange 更新 state。
  • 优点是数据可控,适合校验、联动、提交前处理。

什么是非受控组件?

  • 表单值由 DOM 自己维护,React 通常通过 ref 获取值。
  • 写法简单,适合简单表单或和第三方 DOM 库集成。
  • 缺点是状态不完全由 React 管理,复杂交互不好控制。

Fragment 有什么用?

  • Fragment 可以让组件返回多个相邻元素。
  • 它不会额外生成 DOM 节点。
  • 常用于列表、表格结构或避免无意义包裹层。

React Portal 是什么?

  • Portal 可以把子节点渲染到父组件 DOM 层级以外的位置。
  • 常用于 Modal、Popover、Tooltip、全局 Toast。
  • 它不改变 React 组件树关系,事件冒泡仍按 React 树处理。

React 中事件机制有什么特点?

  • React 使用合成事件,屏蔽不同浏览器事件差异。
  • 事件处理函数通常写成 onClick={handleClick}
  • 不要在 render 中无脑创建复杂匿名函数,列表场景可能造成额外渲染压力。

React 什么时候会重新渲染?

  • 组件自身 state 更新时会重新渲染。
  • 父组件重新渲染时,子组件默认也会重新执行渲染。
  • props、context 值变化也可能触发相关组件重新渲染。

重新渲染一定会更新真实 DOM 吗?

  • 不一定。
  • React 会先重新计算虚拟 DOM,再通过 diff 判断哪些真实 DOM 需要更新。
  • 组件函数重新执行和真实 DOM 变化不是一回事。

Virtual DOM 的作用是什么?

  • Virtual DOM 是 UI 的 JavaScript 对象描述。
  • React 通过它比较新旧 UI,计算最小更新。
  • 它的价值不是一定比手写 DOM 快,而是让 UI 更新更声明式、更可维护。

React diff 的核心规则是什么?

  • 不同类型元素会被认为是不同树,通常直接替换。
  • 同类型元素会比较属性和子节点。
  • 列表子节点主要依赖 key 来判断复用关系。

什么是 reconciliation?

  • reconciliation 是 React 根据新旧元素树计算更新的过程。
  • 它决定组件是复用、更新、卸载还是重新创建。
  • key、组件类型和节点位置都会影响 reconciliation 结果。

什么是 Fiber?

  • Fiber 是 React 内部的协调数据结构。
  • 它让渲染工作可以被拆分、暂停、恢复和按优先级调度。
  • Concurrent Rendering、Suspense 等能力都依赖 Fiber 架构。

render 阶段和 commit 阶段有什么区别?

  • render 阶段负责计算 UI 差异,可以被中断。
  • commit 阶段负责把变更提交到 DOM,不能被中断。
  • 副作用、DOM 更新、ref 更新都发生在 commit 相关阶段。

什么是批处理更新?

  • React 会把同一事件或任务中的多次状态更新合并成一次渲染。
  • 这样可以减少不必要的重复计算和 DOM 更新。
  • React 18 以后,异步场景中的自动批处理能力更强。

setState 后为什么不能马上读到最新值?

  • state 更新可能被批处理,不一定同步提交。
  • 如果下一次更新依赖上一次 state,应该使用函数式更新。
  • 例如:setCount(count => count + 1)

函数式更新有什么用?

  • 可以拿到 React 提供的最新 state。
  • 避免闭包里读取到旧值。
  • 适合连续加减、队列更新、依赖旧状态的场景。

StrictMode 有什么作用?

  • StrictMode 只在开发环境启用额外检查。
  • 它可以帮助发现不安全副作用、过时 API 和不符合规则的写法。
  • 开发环境中某些函数会被额外调用,不代表生产环境也会这样。

Hooks 解决了什么问题?

  • Hooks 让函数组件可以使用状态、副作用、上下文等能力。
  • 它避免了 class 组件中 this、生命周期分散和逻辑复用困难的问题。
  • 自定义 Hook 可以把可复用逻辑抽成函数。

Hooks 的使用规则是什么?

  • 只能在函数组件或自定义 Hook 中调用。
  • 只能在顶层调用,不能放在条件、循环、嵌套函数中。
  • 规则的本质是保证每次渲染 Hook 调用顺序一致。

useState 适合管理什么状态?

  • 适合组件内部简单状态,比如输入框、开关、当前 tab。
  • 状态更新依赖旧值时使用函数式更新。
  • 多个状态强相关时,可以考虑合并对象或改用 useReducer

useEffect 适合做什么?

  • 适合处理组件渲染后的副作用,比如请求、订阅、定时器、手动操作 DOM。
  • Effect 不应该用来计算可以在 render 中直接得到的数据。
  • 依赖数组要真实表达 Effect 使用到的响应式值。

useEffect 的清理函数什么时候执行?

  • 组件卸载时会执行清理函数。
  • 依赖变化时,会先执行上一次清理函数,再执行新的 Effect。
  • 常用于取消订阅、清理定时器、取消请求。

useEffect 和 useLayoutEffect 的区别?

  • useEffect 在浏览器绘制后执行,不阻塞页面展示。
  • useLayoutEffect 在浏览器绘制前同步执行,会阻塞绘制。
  • 测量布局、同步修改 DOM 样式时可用 useLayoutEffect,普通副作用优先用 useEffect

useMemo 有什么用?

  • useMemo 用来缓存计算结果。
  • 适合昂贵计算、稳定引用、避免子组件无意义渲染。
  • 不要为了所有普通变量都套 useMemo,它本身也有维护成本。

useCallback 有什么用?

  • useCallback 用来缓存函数引用。
  • 常用于传给 React.memo 子组件,或作为其他 Hook 的依赖。
  • 如果子组件没有依赖引用稳定性,使用收益可能很小。

useRef 有什么用?

  • 可以保存一个可变值,修改它不会触发渲染。
  • 常用于获取 DOM、保存定时器 ID、保存上一次值。
  • 不要把需要驱动 UI 的数据放进 ref,应该放 state。

useContext 有什么优缺点?

  • 优点是解决跨层级传值,适合主题、语言、登录用户等低频全局数据。
  • 缺点是 context value 变化会让消费组件重新渲染。
  • 高频状态建议拆分 context,或使用状态管理库、selector 方案。

useReducer 适合什么场景?

  • 状态结构复杂,或者更新逻辑依赖 action 类型时适合。
  • 它能把状态更新逻辑集中到 reducer 中。
  • 表单、多步骤流程、复杂筛选条件都可以考虑 useReducer

useImperativeHandle 有什么用?

  • 它可以自定义父组件通过 ref 能访问的方法。
  • 常用于封装输入框、弹窗、播放器等需要暴露命令式能力的组件。
  • 不要滥用,能用 props 控制就优先用声明式 props。

useId 用来做什么?

  • useId 用来生成稳定唯一 ID。
  • 常用于 label 和 input 的可访问性关联。
  • 它能避免服务端渲染和客户端 hydrate 时 ID 不一致。

自定义 Hook 的本质是什么?

  • 自定义 Hook 是复用状态逻辑的函数。
  • 命名必须以 use 开头,这样 lint 和 React 规则才能识别。
  • 自定义 Hook 复用的是逻辑,不复用状态本身;每次调用都有独立状态。

useEffect 常见错误有哪些?

  • 把可以直接计算的数据放进 Effect 再 setState,导致多一次渲染。
  • 依赖数组漏依赖,造成闭包读取旧值。
  • 在 Effect 中请求数据但不处理竞态,快速切换参数时可能展示旧结果。

如何处理 Effect 请求竞态?

  • 可以用 AbortController 在清理函数中取消旧请求。
  • 也可以用请求序号,只接收最后一次请求结果。
  • 使用 TanStack Query 这类库时,很多缓存和竞态问题会被封装掉。

什么是闭包陷阱?

  • 函数组件每次渲染都会创建新的作用域。
  • 异步回调、定时器、事件监听可能读到当次渲染时的旧 state。
  • 可以用依赖数组、函数式更新、ref 或 useEffectEvent 处理。

React 19 的 Actions 是什么?

  • Actions 是 React 19 中处理异步提交的一套模式。
  • 它可以把 pending、错误、乐观更新等状态和提交动作绑定起来。
  • 常见场景是表单提交、服务端函数调用、异步更新 UI。

useActionState 有什么用?

  • useActionState 用来根据 Action 的返回值更新组件状态。
  • 它通常用于表单提交,可以拿到上一次提交结果和 pending 状态。
  • 比自己手写 loading、error、result 状态更集中。

useFormStatus 有什么用?

  • useFormStatus 可以读取当前表单的提交状态。
  • 常见用法是在提交按钮里拿到 pending,从而禁用按钮或展示 loading。
  • 它需要在 form 的子组件中使用。

useOptimistic 有什么用?

  • useOptimistic 用于乐观更新。
  • 请求还没完成时先更新 UI,让交互更快。
  • 如果请求失败,再根据真实结果回滚或展示错误。

React 19 中 ref 有什么变化?

  • React 19 支持函数组件把 ref 当作普通 prop 接收。
  • 很多场景不再需要 forwardRef
  • 但老项目和库兼容时,仍可能继续看到 forwardRef 写法。

React Server Components 是什么?

  • Server Components 是在服务端执行的 React 组件。
  • 它不会把组件代码发送到浏览器,因此可以减少客户端 JS。
  • 它适合读取数据库、文件系统、内部服务等服务端资源。

Server Components 和 SSR 有什么区别?

  • SSR 是一种把组件渲染成 HTML 的渲染方式。
  • Server Components 是一种组件模型,决定组件运行在服务端还是客户端。
  • 两者可以结合使用,但概念不同。

Server Components 能使用 useState 吗?

  • 不能。
  • Server Components 不在浏览器中运行,也不持有客户端交互状态。
  • 需要交互时,要把那部分拆成 Client Component。

“use client” 是什么?

  • "use client" 标记当前模块进入客户端组件边界。
  • 该模块及其依赖会被打包到客户端。
  • 使用 state、effect、浏览器 API、事件处理时,需要客户端组件。

“use server” 是什么?

  • "use server" 用来声明可被客户端调用的服务端函数。
  • 它不是 Server Component 的标记。
  • Server Function 的参数和返回值需要可序列化,并且要注意权限校验。

Client Component 可以导入 Server Component 吗?

  • 通常不能直接导入并执行 Server Component。
  • 可以通过 props 的 children 等方式组合,让服务端先渲染好再传入。
  • 核心原则是不要把服务端代码拉进客户端 bundle。

Server Components 适合什么场景?

  • 适合数据读取、静态内容、权限内聚、减少客户端包体积。
  • 不适合点击、输入、拖拽等浏览器交互。
  • 实际项目中常见做法是服务端组件负责数据和布局,客户端组件负责交互。

React 19.2 的 Activity 有什么用?

  • <Activity /> 可以把 UI 分成可控制优先级的活动区域。
  • hidden 模式会隐藏内容、卸载副作用、保留状态,并把更新延后。
  • 适合预渲染用户可能访问的页面,或保留离开页面的输入状态。

useEffectEvent 解决什么问题?

  • 它可以把 Effect 中的“事件逻辑”拆出来。
  • 拆出来的函数能读到最新 props 和 state,但不需要放进依赖数组。
  • 适合订阅连接成功、定时器 tick、事件监听回调这类场景。

useEffectEvent 和 useCallback 有什么区别?

  • useCallback 是稳定函数引用,依赖变化时会重新生成。
  • useEffectEvent 是给 Effect 内部事件使用,能读取最新值但不触发 Effect 重跑。
  • 不能把 useEffectEvent 当作普通事件处理函数到处传。

React 19.2 的 cacheSignal 是什么?

  • cacheSignal 用在 React Server Components 中。
  • 它可以感知 cache() 生命周期结束,从而中止不再需要的异步任务。
  • 常用于服务端缓存和请求取消相关场景。

React 19.2 的 Performance Tracks 是什么?

  • Performance Tracks 会在浏览器性能面板中展示 React 相关性能轨迹。
  • 它帮助定位渲染、更新、Suspense 等性能问题。
  • 面试中可以说它提升了 React 应用性能分析的可观测性。

React Compiler 是什么?

  • React Compiler 是构建时优化工具。
  • 它理解 React 规则,可以自动做 memoization,减少手写 memouseMemouseCallback
  • 它主要优化更新性能,不是用来替代所有业务层缓存。

React Compiler 能完全替代 useMemo 吗?

  • 不能说完全替代。
  • 新代码可以更多依赖 Compiler 自动优化。
  • 但需要精确控制引用稳定性、Effect 依赖或跨组件共享缓存时,仍可手写 useMemo / useCallback

React Compiler 的 “use memo” 是什么?

  • "use memo" 是 React Compiler 的指令。
  • 在 annotation 模式下,它可以显式标记函数需要被编译优化。
  • 一般 infer 模式下不需要到处写,组件命名规范更重要。

React Compiler 的 “use no memo” 是什么?

  • "use no memo" 可以让某个函数跳过 Compiler 优化。
  • 常用于调试问题或处理暂时不兼容的代码。
  • 它是增量接入 Compiler 时的逃生口。

React.memo 有什么用?

  • React.memo 可以在 props 没有变化时跳过组件重新渲染。
  • 适合渲染成本较高、props 相对稳定的组件。
  • 如果 props 每次都是新对象或新函数,memo 效果会变差。

React.memo 为什么不一定提升性能?

  • memo 需要比较 props,这本身也有成本。
  • 组件很简单时,比较成本可能比重新渲染还高。
  • props 经常变化时,memo 几乎没有收益。

如何优化长列表?

  • 使用虚拟列表,只渲染可视区域数据。
  • 列表项使用稳定 key,避免 index。
  • 拆分行组件,配合 memo 和稳定 props。
  • 接口层做分页、无限滚动或分片加载。

什么是虚拟列表?

  • 虚拟列表只渲染可视区域附近的元素。
  • 它通过占位高度模拟完整列表滚动条。
  • 适合几千、几万条数据的表格、日志、消息列表。

如何优化首屏性能?

  • 路由级代码分割,减少首屏 JS。
  • 图片压缩、懒加载、使用合适尺寸。
  • SSR / SSG 提前生成 HTML。
  • 减少首屏无关依赖和阻塞请求。

Suspense 解决什么问题?

  • Suspense 用声明式方式描述异步等待时的 fallback UI。
  • 常用于组件懒加载、数据加载、Server Components 流式渲染。
  • 它让加载状态从命令式 if 判断变成组件边界。

lazy 和 Suspense 如何配合?

  • React.lazy 使用动态 import() 懒加载组件。
  • Suspense 包裹懒加载组件,并提供 fallback。
  • 常用于路由切割和非首屏组件加载。

useTransition 有什么用?

  • useTransition 可以把某些更新标记为非紧急更新。
  • 输入框输入、点击反馈等紧急交互会优先响应。
  • 常用于搜索过滤、大列表切换、复杂 tab 内容更新。

useDeferredValue 有什么用?

  • useDeferredValue 可以延迟某个值的更新。
  • 常用于输入框关键字变化后,延迟更新昂贵列表。
  • 它和防抖不同,不是固定时间等待,而是让 React 根据渲染压力调度。

如何减少子组件无意义渲染?

  • 合理拆分组件,把变化范围缩小。
  • 使用稳定 props,避免每次 render 创建新对象。
  • 必要时使用 React.memouseMemouseCallback
  • 使用 Profiler 先定位,不要盲目优化。

如何判断 React 性能问题出在哪里?

  • 用 React DevTools Profiler 看哪些组件频繁渲染。
  • 用浏览器 Performance 看脚本、渲染、布局、网络耗时。
  • 分清是渲染慢、请求慢、资源大,还是主线程被阻塞。

Redux 的核心原则是什么?

  • 单一数据源:全局状态集中在 store。
  • 状态只读:通过 dispatch action 描述变化。
  • 纯函数更新:reducer 根据旧 state 和 action 返回新 state。

Redux Toolkit 解决了什么问题?

  • 减少传统 Redux 的模板代码。
  • createSlice 把 action 和 reducer 写在一起。
  • configureStore 内置常用中间件和开发体验。

Redux、Zustand、Context 怎么选?

  • Context 适合低频全局数据,比如主题、语言、用户信息。
  • Zustand 更轻量,适合中小型项目或局部全局状态。
  • Redux Toolkit 更适合大型团队、复杂状态流、强约束项目。

什么是服务端状态?

  • 服务端状态指来自接口、数据库或远端缓存的数据。
  • 它需要处理缓存、刷新、失效、重试、并发和加载状态。
  • 常用 TanStack Query、SWR 或框架数据层管理。

为什么不建议把接口数据都放 Redux?

  • 接口数据有缓存、过期、重试、分页等服务端状态问题。
  • Redux 更适合客户端业务状态,不天然解决请求生命周期。
  • 用 Query 类库管理服务端状态通常更省心。

React Query / TanStack Query 解决什么问题?

  • 管理接口请求、缓存、刷新、重试、分页和乐观更新。
  • 减少手写 loading、error、data、refetch 状态。
  • 适合把服务端状态从 UI 状态中拆出来。

React Router v6 的特点是什么?

  • 使用 Routeselement 定义路由。
  • 支持嵌套路由和 Outlet
  • 路由匹配规则更清晰,不再依赖 v5 的 Switch

嵌套路由怎么实现?

  • 父路由组件中放 <Outlet />
  • 子路由配置在父路由 children 中。
  • 适合后台系统 layout、详情页 tab、分模块页面。

前端权限一般怎么设计?

  • 登录态:判断用户是否登录。
  • 路由权限:控制页面能否访问。
  • 菜单权限:控制导航是否展示。
  • 按钮权限:控制操作入口。
  • 接口权限:真正安全必须由后端校验。

如何设计一个 Modal 组件?

  • 支持受控的 openonClose
  • 使用 Portal 渲染到 body 下,避免层级和 overflow 问题。
  • 支持 Esc 关闭、遮罩关闭、滚动锁定、焦点管理。
  • 注意 aria 属性,保证可访问性。

如何设计一个表单组件?

  • 要考虑字段值、校验、错误信息、提交状态、默认值和重置。
  • 复杂表单可以使用 React Hook Form、Formik 或业务表单引擎。
  • 服务端错误要能回显到具体字段或全局提示。

如何封装一个业务组件?

  • 先区分通用能力和业务规则,不要过早抽象。
  • props 设计要稳定,避免暴露过多内部细节。
  • 复杂组件要提供受控和非受控两种使用方式。
  • 文档、示例和边界状态比组件代码本身同样重要。

什么是 props drilling?

  • props drilling 是为了把数据传给深层组件而层层透传 props。
  • 它会让中间组件承担不属于自己的参数。
  • 可以用组合模式、Context、状态管理或调整组件结构解决。

组合模式如何减少 props drilling?

  • 父组件可以直接把子组件作为 children 或插槽传入。
  • 中间组件只负责布局,不需要知道深层组件需要什么数据。
  • 这种方式比滥用全局状态更轻。

Error Boundary 有什么用?

  • Error Boundary 可以捕获子组件渲染阶段错误。
  • 它能展示兜底 UI,避免整个页面白屏。
  • 常用于路由级、模块级、组件库级边界。

Error Boundary 不能捕获哪些错误?

  • 不能直接捕获事件处理函数里的错误。
  • 不能直接捕获异步回调里的错误。
  • 不能捕获服务端渲染中的所有错误,需要框架配合。

React 项目如何做代码分割?

  • 路由级使用动态 import()
  • 大型第三方库按需加载。
  • 非首屏组件延迟加载。
  • 分析 bundle,避免把所有依赖塞进主包。

React 项目如何做测试?

  • 单元测试覆盖工具函数和核心逻辑。
  • 组件测试关注用户行为,不要过度测试实现细节。
  • E2E 测试覆盖登录、下单、提交等核心链路。
  • 常见工具有 Vitest、Jest、Testing Library、Playwright。

React 中如何做可访问性?

  • 表单 label 和 input 要正确关联。
  • 弹窗要处理焦点、Esc、aria 属性。
  • 按钮用 button,不要用 div 假装按钮。
  • 图片提供合理 alt,图标按钮提供可读标签。

React 中如何处理样式?

  • 小项目可以用 CSS Modules、Sass。
  • 组件库常用 CSS-in-JS、Tailwind 或样式变量。
  • 团队要统一方案,避免全局样式污染。
  • 设计系统项目要关注 token、主题和暗色模式。

React 中如何处理国际化?

  • 文案不要硬编码在组件逻辑中。
  • 使用 key 管理多语言资源。
  • 注意日期、数字、货币、复数和时区。
  • 路由、SEO、服务端渲染也要考虑语言环境。

React 中如何避免内存泄漏?

  • 清理定时器、事件监听、订阅。
  • 请求组件卸载后不要继续 setState。
  • 第三方实例要在清理函数中 destroy。
  • 长列表、图表、编辑器尤其要关注释放资源。

React 中为什么不要在 render 中写副作用?

  • render 应该是纯计算,输入相同输出相同。
  • 副作用放 render 中可能被重复执行。
  • 请求、订阅、手动 DOM 操作应该放 Effect 或事件处理函数中。

React 中为什么不要滥用 useEffect?

  • 很多派生数据可以直接在 render 中计算。
  • 滥用 Effect 容易导致多一次渲染、依赖混乱和状态不同步。
  • Effect 应该用于和外部系统同步,而不是普通数据转换。

如何理解组件纯函数?

  • 给定相同 props、state、context,组件应返回相同 UI。
  • 不应该在渲染过程中修改外部变量或触发请求。
  • 纯组件更容易测试,也更适合 React 调度和优化。

如何设计组件的 state?

  • 能从 props 或其他 state 推导出的值,不要重复存 state。
  • 多个 state 同时变化且强相关,可以合并管理。
  • 状态尽量放在真正需要它的最近公共父组件。

什么是状态提升?

  • 多个组件需要共享状态时,把状态放到它们最近的公共父组件。
  • 父组件通过 props 下发数据,通过回调接收子组件事件。
  • 状态提升是解决兄弟组件通信的基础方式。

什么是组件受控和非受控设计?

  • 受控组件由外部传入 value 和 onChange。
  • 非受控组件内部自己维护状态,外部只给 defaultValue。
  • 通用组件最好根据场景支持其中一种或两种模式。

如何封装一个 useDebounce?

import { useEffect, useState } from 'react'

function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value)

useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)

return () => clearTimeout(timer)
}, [value, delay])

return debouncedValue
}

如何封装一个 usePrevious?

import { useEffect, useRef } from 'react'

function usePrevious(value) {
const ref = useRef()

useEffect(() => {
ref.current = value
}, [value])

return ref.current
}

如何封装一个 useToggle?

import { useCallback, useState } from 'react'

function useToggle(defaultValue = false) {
const [value, setValue] = useState(defaultValue)

const toggle = useCallback(() => {
setValue(v => !v)
}, [])

return [value, toggle, setValue]
}

如何封装一个 useClickOutside?

import { useEffect } from 'react'

function useClickOutside(ref, handler) {
useEffect(() => {
function onMouseDown(event) {
if (!ref.current || ref.current.contains(event.target)) return
handler(event)
}

document.addEventListener('mousedown', onMouseDown)
return () => document.removeEventListener('mousedown', onMouseDown)
}, [ref, handler])
}

如何封装一个 useInterval?

import { useEffect, useRef } from 'react'

function useInterval(callback, delay) {
const callbackRef = useRef(callback)

useEffect(() => {
callbackRef.current = callback
}, [callback])

useEffect(() => {
if (delay == null) return

const timer = setInterval(() => {
callbackRef.current()
}, delay)

return () => clearInterval(timer)
}, [delay])
}

如何封装一个请求 Hook?

  • 至少要处理 loadingdataerrorrun 或自动请求。
  • 要处理组件卸载、参数变化和请求竞态。
  • 复杂场景建议用 TanStack Query,不要重复造完整请求缓存系统。

如何回答 React 性能优化题?

  • 先说定位:Profiler、Performance、Network。
  • 再说渲染:拆组件、memo、稳定 props、虚拟列表。
  • 再说资源:代码分割、图片优化、缓存、SSR/SSG。
  • 最后说不要盲目优化,要用数据验证收益。

如何回答 React 19 新特性?

  • 可以从 Actions、表单相关 Hook、乐观更新、ref as prop、RSC 这几块说。
  • 如果追问 React 19.2,可以补充 Activity、useEffectEvent、cacheSignal、Performance Tracks。
  • 回答时最好结合场景,比如表单提交、隐藏页面保留状态、Effect 闭包问题。

如何回答 Server Components?

  • 先说它运行在服务端,可以减少客户端 JS。
  • 再说它不能用交互 Hook,需要和 Client Component 组合。
  • 最后说 "use client" 是客户端边界,"use server" 是服务端函数,不要混淆。

如何回答 React Compiler?

  • 它是构建时优化工具,自动做 memoization。
  • 它能减少手写 memo 相关代码,但不是所有性能问题的万能药。
  • 迁移时要增量接入,用工具发现不符合 React 规则的代码。

面试中如何讲项目里的 React 优化实践?

  • 不要只背 API,要结合项目问题。
  • 示例:表格卡顿用虚拟列表,搜索卡顿用 transition 或 deferred value。
  • 示例:首屏慢用路由懒加载、减少包体、图片优化。
  • 示例:重复请求和缓存混乱,用 TanStack Query 管理服务端状态。

参考资料