React 前端中高级面试题集
针对上海缔歌科技「前端开发」岗位(16-20K·13薪,3-5年经验)整理的针对性技术面试题。覆盖 React / Next.js / TypeScript、性能优化、工程化、微前端、CI/CD、安全、监控与 A/B 实验等核心要求,每题均附参考答案与解析。
React 核心与 Hooks
React 函数组件中,useEffect 的依赖数组容易出现哪些陷阱?如何正确避免闭包陈旧(stale closure)问题?
常见陷阱有三类:
- 依赖遗漏:只在
useEffect里使用了某个状态/属性,但没写入依赖数组,导致回调中的值是旧的。 - 依赖过度:把对象、函数、数组直接放进依赖数组,因为每次渲染引用都变,导致 effect 频繁执行。
- 异步回调闭包:在
useEffect里启动定时器或异步请求,内部读取的是“创建那一帧”的状态,造成 stale closure。
应对方案:
- 使用
eslint-plugin-react-hooks的exhaustive-deps规则自动检查依赖。 - 对会变化的函数用
useCallback,对会变化的复杂对象用useMemo,保证引用稳定。 - 如果只想监听某个“动作”而非状态值,可用
useRef保存最新值,在 effect 中读取 ref.current;或者用函数式状态更新setState(prev => ...)避免依赖旧状态。 - 清理副作用:异步请求要在 cleanup 中取消(AbortController / 取消标记),防止组件卸载后 setState。
岗位要求“精通 React 框架及其核心理念(Hooks、Context、组件化思想、虚拟 DOM 等)”。这道题考察候选人是否能写出稳定、可维护的副作用代码,以及是否具备工程化 lint 意识,是 3-5 年经验面试的高频题。
useMemo 与 useCallback 分别解决了什么问题?什么情况下不建议使用?
useMemo 缓存“计算结果”,避免昂贵计算在每次渲染时重复执行;useCallback 缓存“函数引用”,避免子组件因为收到新的函数 prop 而触发不必要的重渲染(配合 React.memo 使用)。
不建议使用的情况:
- 计算成本很低:比如简单的字符串拼接、数组 map 生成 JSX,缓存的收益抵不过比较依赖的开销。
- 依赖变化非常频繁:每次依赖都变,缓存命中率低,反而增加代码复杂度。
- 过早优化:没有通过 React DevTools Profiler 或性能测试确认瓶颈,盲目加 memo 会让代码难以维护。
- 函数只在事件回调中使用、不向下传递:可以直接在事件处理函数里写逻辑,不需要 useCallback。
最佳实践:先测量,再优化;把 useMemo / useCallback 当作“昂贵的优化手段”而非默认写法。
岗位 JD 强调“性能优化”“复杂交互页面开发及重构经验”。通过本题可判断候选人是否理解 React 渲染模型,以及是否会为了优化而优化——后者在大型代码库中容易引入 bug。
React 18 引入了哪些并发特性?请说明 useTransition 与 useDeferredValue 的适用场景。
React 18 的核心并发特性包括:Concurrent Rendering、Automatic Batching、Suspense 增强、Transitions、useDeferredValue、useId、useSyncExternalStore 等。
- useTransition:把“非紧急更新”标记为 transition,让高优先级交互(如输入)不被阻塞。典型场景:搜索框输入时即时反馈输入框内容,但列表过滤结果允许延迟渲染。
- useDeferredValue:让某个值延迟更新,保持 UI 响应。典型场景:在快速输入时,用 deferred 值驱动的图表/列表慢半拍更新,而输入框本身保持即时响应。
两者都用于“可中断渲染 + 优先级调度”,但 useTransition 适合控制“状态更新”的优先级,useDeferredValue 适合控制“某个 prop/state 的滞后反映”。
JD 要求“关注前沿前沿技术与生态发展”。React 18 并发是近两年前端生态的关键变化,3-5 年经验者至少应了解概念和落地场景,最好能在项目中有真实应用。
Next.js 与服务端渲染
Next.js App Router 与 Pages Router 在渲染模型、数据获取、路由机制上有哪些核心差异?
| 维度 | Pages Router | App Router |
|---|---|---|
| 渲染模型 | 默认 CSR/SSR,页面级 getServerSideProps / getStaticProps / getInitialProps | Server Components 为默认,可嵌套使用;Client Components 需显式声明 'use client' |
| 数据获取 | 在页面 export 数据获取函数 | 在 Server Component 中直接 await fetch() 或访问数据库;可按组件级获取数据 |
| 路由约定 | pages/index.tsx | app/page.tsx、app/layout.tsx |
| 加载/错误状态 | 自定义 pages/_app.tsx 处理 | 基于文件约定的 loading.tsx、error.tsx、not-found.tsx |
| 嵌套布局 | 依赖 _app 和自定义组件 | 原生支持 layout.tsx 嵌套,状态可保留 |
App Router 的优势:减少客户端 JS、提升首屏性能、组件级数据获取更灵活、更好的流式渲染(Streaming SSR)支持。
JD 明确要求“熟悉 Next.js 服务端渲染框架”。候选人需要区分两代路由,尤其是 Server Components 对bundle 体积和首屏性能的影响,这直接关系到岗位里的性能优化职责。
请对比 SSR、SSG、ISR、CSR 四种渲染模式,并说明在 Next.js 中如何选择?
| 模式 | 构建时/请求时 | 首屏 | 适用场景 |
|---|---|---|---|
| CSR | 运行时浏览器渲染 | 慢(空壳 HTML) | 后台管理、强交互 SPA、对 SEO 无要求 |
| SSG | 构建时生成 HTML | 最快,可 CDN 缓存 | 营销页、博客、文档、商品详情(内容不常变) |
| SSR | 每次请求服务端渲染 | 较快,动态数据 | 个性化页面、需要实时数据的页面 |
| ISR | 构建时 + 增量再生成 | 接近 SSG,可后台更新 | 大型电商列表、新闻站、内容频繁更新但又要求高并发 |
选择策略:以“内容新鲜度”和“请求量”为横纵轴。内容固定 + 流量大 → SSG;内容偶尔更新 + 流量大 → ISR;强个性化/实时 → SSR;重交互/后台 → CSR。
JD 提到“SSR(服务端渲染)、静态站点生成、动态路由、懒加载等技术手段,提升页面加载速度”。本题考察候选人是否能根据业务场景做渲染策略决策,是架构设计能力的体现。
在 Next.js App Router 中,如何实现动态路由与静态生成?请说明 generateStaticParams 的作用。
App Router 的动态路由使用文件夹约定,例如 app/blog/[slug]/page.tsx 对应 /blog/:slug。
若要在构建时静态生成这些动态页面,需要在 page 组件中导出 generateStaticParams:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json());
return <article>{post.title}</article>;
}
generateStaticParams 的作用:
- 告诉 Next.js 在构建时要生成哪些路由参数组合。
- 未在构建时生成的参数可在运行时通过 SSR fallback 处理(默认
dynamicParams = true)。 - 结合 ISR 可实现增量更新。
岗位 JD 明确列出“动态路由”和“静态站点生成”。本题测试候选人是否掌握 App Router 下的新 API,而不是只停留在 Pages Router 的 getStaticPaths。
Vue 核心与生态
Vue 2 与 Vue 3 的响应式原理有什么区别?为什么 Vue 3 选择 Proxy 替代 Object.defineProperty?
Vue 2 使用 Object.defineProperty 遍历对象属性,为每个属性设置 getter/setter 实现依赖收集与触发更新。数组变异方法(push、pop、splice 等)需要被重写才能追踪变化。
Vue 3 改用 Proxy 代理整个对象,拦截 get/set/deleteProperty/has/ownKeys 等操作。新增属性、删除属性、数组索引修改、Map/Set/WeakMap/WeakSet 都能被正常追踪。
选择 Proxy 的原因主要有三点:
- 更完整的拦截能力:可以监听到属性新增、删除、in 操作符、Object.keys 等,defineProperty 无法做到。
- 更好的数组性能:不需要重写数组原型方法,直接通过 Proxy 拦截索引访问和长度变化。
- 嵌套对象懒代理:Vue 3 只在访问嵌套对象时才创建 Proxy,避免初始化时深度递归带来的性能开销。
JD 要求"精通 Vue / React 等主流前端框架",响应式是 Vue 最核心的知识点。面试官希望通过对比考察候选人对框架底层实现的理解深度,而不仅是 API 使用。
Options API 与 Composition API 各适合什么场景?你在项目中如何选择?
Options API 通过 data、methods、computed、watch 等选项组织代码,逻辑分散在不同选项中。适合小型组件、快速上手、团队 Vue 2 迁移期。
Composition API 通过 setup() 或 <script setup> 按逻辑关注点组织代码,配合 ref / reactive / computed / watch / lifecycle hooks 等函数复用逻辑。适合复杂组件、逻辑复用、TypeScript 类型推断、大型项目。
选择建议:
- 简单展示型组件:Options API 更直观。
- 复杂业务组件、表单、表格、多步骤流程:Composition API,逻辑内聚,便于抽离 composables。
- 需要跨组件复用逻辑:封装 composable(如 useUser、useTable、useAsync)。
JD 强调"Vue 组合式 API 实战",这是明确考点。回答时要避免捧一踩一,重点说明 Composition API 在逻辑组织和复用上的优势,并能结合项目举例。
Vue 组件通信有哪些方式?请按使用场景分类说明。
| 场景 | 方式 | 说明 |
|---|---|---|
| 父子组件 | props / emit | 父传子 props,子传父 emit,单向数据流。 |
| 父级直接访问子级 | ref / $parent(不推荐) | ref 获取组件实例,适合调用子组件暴露的方法。 |
| 跨层级 / 全局 | Provide / Inject | 祖先提供数据,后代注入,适合主题、配置等。 |
| 全局状态 | Pinia / Vuex | 正式项目首选 Pinia,轻量、类型友好、Devtools 支持好。 |
| 事件总线 | mitt / EventBus | 适合轻量跨组件事件,Vue 3 已移除 $on/$off。 |
| URL 状态共享 | Vue Router query/params | 页面级状态,刷新不丢失。 |
岗位 JD 提到"组件化开发"和"状态管理",通信方式是基础但高频的考点。回答时强调 Vue 3 推荐方案(props/emit、Pinia、Provide/Inject、mitt),避免提及已废弃的 Vue 2 选项。
Vue Router 4 有哪些核心变化?如何设计一个带权限的路由守卫?
Vue Router 4 核心变化:
- 创建方式改为
createRouter+createWebHistory / createWebHashHistory。 - 路由匹配改为基于
path-to-regexp的自定义实现,通配符*需要显式参数。 - 导航守卫逻辑调整,
beforeEnter支持数组;next()不再推荐传递参数。 - 新增
scrollBehavior、router.addRoute / removeRoute动态路由能力。
权限路由守卫示例:
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
if (!userStore.token && to.path !== '/login') {
return next('/login');
}
if (to.meta.roles && !to.meta.roles.includes(userStore.role)) {
return next('/403');
}
next();
});
JD 要求"前端路由"和"权限控制"经验。考察候选人是否熟悉 Vue Router 4 的 API 变化,以及能否将路由守卫与真实权限模型(token、角色、动态菜单)结合。
Vue 3 中 ref 和 reactive 有什么区别?什么情况下会用到 toRef / toRefs?
ref 接收任意类型值,返回一个响应式对象 { value: ... }。模板中会自动解包,JS 中需要 .value 访问。适合原始值(string、number、boolean)或需要替换整个对象的场景。
reactive 只能接收对象类型,返回 Proxy 代理对象。访问属性无需 .value,但不能直接替换整个对象(会丢失响应式)。
toRef / toRefs 用于从 reactive 对象中解构出响应式引用:
toRef(state, 'count'):创建一个与 state.count 保持同步的 ref。toRefs(state):将 reactive 对象所有属性转为 ref,常用于从 composable 中解构返回。
const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 解构后仍保持响应式
这是 Composition API 的基础考点,也是最容易写错的地方。面试官希望看到候选人对响应式引用、解构丢失响应式问题以及解决方案有清晰认识。
你在 Vue 项目中做过哪些性能优化?请至少说出 5 条并说明适用场景。
- v-once / v-memo:静态内容或极少变化的数据使用
v-once;列表中部分子树依赖不变时用v-memo。 - 懒加载组件:
defineAsyncComponent+ 路由懒加载,减少首屏 bundle 体积。 - 合理使用 computed:避免在模板中写复杂表达式,缓存计算结果。
- 列表渲染优化:
v-for必加:key;大数据列表使用虚拟滚动(vue-virtual-scroller)。 - 避免不必要响应式:大量静态数据用
Object.freeze或shallowRef / shallowReactive跳过深层响应式。 - Tree-shaking 与按需引入:组件库(Element Plus、Ant Design Vue)按需引入,避免全量打包。
- keep-alive 缓存:多标签页、表单步骤等场景缓存组件状态,减少重复渲染。
JD 明确要求"前端性能优化",且薪资档位属于中高级。回答时要结合 Vue 特性(v-memo、defineAsyncComponent、keep-alive、shallowRef),并给出具体场景,避免泛泛而谈。
TypeScript 类型设计
如何为一个复杂的 React 组件设计类型安全的 Props?请用示例说明泛型、条件类型、联合类型的应用。
以“按钮组件支持多种变体且不同变体拥有不同专属属性”为例:
type BaseButtonProps = {
children: React.ReactNode;
disabled?: boolean;
onClick?: () => void;
};
type PrimaryButtonProps = BaseButtonProps & {
variant: 'primary';
themeColor?: string;
};
type LinkButtonProps = BaseButtonProps & {
variant: 'link';
href: string;
external?: boolean;
};
type ButtonProps = PrimaryButtonProps | LinkButtonProps;
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return <a href={props.href} target={props.external ? '_blank' : undefined}>{props.children}</a>;
}
return <button style={{ color: props.themeColor }}>{props.children}</button>;
}
// 类型校验:link 变体必须传 href
<Button variant="link" href="/home">首页</Button>
要点:
- 用 联合类型 表达互斥变体。
- 用 类型收窄(discriminated union)在运行时分支中自动获得精确类型。
- 用 泛型 处理可复用的高阶组件或 render props,例如
Table<T>。 - 用 Pick/Omit/Partial 从已有类型派生子类型,减少重复定义。
JD 要求“熟练掌握 TypeScript,具备良好的类型设计与工程化规范意识”。本题考察实际编码中如何写出既安全又可扩展的类型,而不是死记硬背概念。
interface 与 type 有什么区别?在大型项目中如何抉择?
| 特性 | interface | type |
|---|---|---|
| 合并声明 | 支持声明合并(declaration merging) | 不支持同名重复声明 |
| 扩展方式 | extends | & 交叉类型 |
| 适用类型 | 仅对象类型 | 对象、联合、元组、基本类型别名等 |
| 错误提示 | 通常更清晰,指出缺少的属性 | 交叉类型冲突时提示较复杂 |
大型项目建议:
- 对象类型、组件 Props、类类型优先用
interface,便于扩展和声明合并。 - 联合类型、工具类型、复杂类型别名用
type。 - 团队统一 lint 规则(如
@typescript-eslint/consistent-type-definitions),避免混用导致风格不一致。
这是 TypeScript 基础但高频的问题。JD 强调“工程化规范意识”,考察候选人是否有团队规范意识,以及是否能解释清楚技术差异。
状态管理与数据流
React 项目中 Redux、Zustand、Jotai、Context 各适合什么场景?你会如何选型?
| 方案 | 特点 | 适用场景 |
|---|---|---|
| Context | React 内置,简单,但任意值变化会导致所有消费组件重渲染 | 主题、语言、用户身份等低频变化的全局状态 |
| Zustand | 轻量、无样板代码、支持 selector、中间件生态丰富 | 中小型项目或希望快速替代 Redux 的场景 |
| Jotai / Recoil | 原子化状态,细粒度订阅,适合派生状态 | 状态之间依赖复杂、需要大量派生计算的 UI |
| Redux Toolkit | 成熟、可预测、DevTools 强大、中间件生态完善 | 大型团队、复杂业务流、需要严格数据流审计的项目 |
选型思路:先看状态规模与变化频率,再看团队熟悉度和调试需求。简单共享状态用 Context;中等规模优先 Zustand 降低样板;超大型或需要强约束的用 Redux Toolkit。
JD 提到“组件化思想、虚拟 DOM、数据流设计”。状态管理选型是前端架构设计的核心,通过本题判断候选人是否能根据项目规模做合理技术决策。
全局状态中的异步副作用(如 API 请求)应该如何处理?请说明 Redux Toolkit + createAsyncThunk 或 Zustand 中的常见做法。
核心原则:异步逻辑不应直接放在 UI 组件里,应下沉到状态管理层或独立的服务层。
- Redux Toolkit:使用
createAsyncThunk定义异步 action,自动派发 pending/fulfilled/rejected,配合 extraReducers 更新 loading/error/data 状态。 - Zustand:在 store 中直接写 async action,手动 set 状态;或者结合
immer中间件简化不可变更新。 - 通用做法:定义统一的 API 层(如
services/),状态管理只负责调用和保存结果;错误处理统一封装,UI 只消费状态。
// Zustand 示例
import { create } from 'zustand';
const useUserStore = create((set) => ({
user: null,
loading: false,
error: null,
fetchUser: async (id) => {
set({ loading: true, error: null });
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('fetch failed');
set({ user: await res.json(), loading: false });
} catch (e: any) {
set({ error: e.message, loading: false });
}
},
}));
JD 强调“数据流设计及性能优化思路”。异步状态管理是实际项目中最容易出问题的环节,考察候选人是否有清晰的分层意识和错误处理经验。
前端性能优化
请列举至少 5 个优化首屏加载(FCP/LCP)的具体手段,并说明它们在 Next.js 或 React 项目中的落地方式。
- 代码分割 / 路由懒加载:React 用
React.lazy + Suspense,Next.js 路由天然代码分割。 - 图片优化:使用 Next.js
Image组件自动提供 WebP/AVIF、响应式尺寸、懒加载;React 项目可用loading="lazy"和srcset。 - 字体优化:使用
font-display: swap,预加载关键字体,避免 FOIT。 - 关键 CSS 内联:把首屏关键 CSS 直接内联到 HTML,减少渲染阻塞。
- 服务端渲染 / 静态生成:减少客户端 JS 执行时间,让 LCP 资源更快呈现。
- Tree Shaking 与第三方库瘦身:按需引入组件(如 lodash-es 替代 lodash),使用
next/bundle-analyzer分析包体积。 - 资源预加载/预连接:对关键域名使用
<link rel="preconnect">,对 LCP 图片使用<link rel="preload">。
JD 明确写到“具备前端性能优化能力,对首屏加载、资源压缩、代码分割、懒加载、缓存等策略有深入理解”。本题是岗位核心能力点,需要候选人能落地而非空谈。
代码分割(Code Splitting)、懒加载(Lazy Loading)与预加载(Prefetch/Preload)有什么区别?如何组合使用?
| 技术 | 目的 | 触发时机 | 典型用法 |
|---|---|---|---|
| Code Splitting | 减小初始 bundle | 构建时按路由/组件拆包 | Webpack/Vite 动态 import,Next.js 自动路由分割 |
| Lazy Loading | 延迟加载非关键资源 | 运行时(进入视口/路由/交互时) | React.lazy + Suspense,图片 loading="lazy" |
| Preload | 提前加载当前页面关键资源 | 当前页面加载早期 | <link rel="preload"> 关键字体/图片/脚本 |
| Prefetch | 预测性加载未来可能用到的资源 | 浏览器空闲时 | Next.js <Link prefetch>,路由预取 |
组合策略:首屏只加载必要资源(code splitting + lazy loading),对首屏关键资源使用 preload,对可能下一步访问的页面/数据使用 prefetch。
JD 把“懒加载”“代码分割”“预加载”列为关键技术手段。本题考察候选人对加载策略体系化的理解,避免混淆概念。
Core Web Vitals 包含哪些核心指标?请分别说明 LCP、CLS、INP 的优化思路。
| 指标 | 含义 | 优化方向 |
|---|---|---|
| LCP(Largest Contentful Paint) | 最大内容元素渲染时间 | 优化图片/视频大小、使用 CDN、预加载 LCP 资源、SSR/SSG、减少 TTFB |
| CLS(Cumulative Layout Shift) | 累积布局偏移 | 为图片/视频/广告预留尺寸、避免在已有内容后插入内容、字体加载策略优化 |
| INP(Interaction to Next Paint) | 交互到下一次绘制延迟 | 减少长任务、事件处理函数非阻塞、使用 useTransition/useDeferredValue、优化 DOM 操作 |
| FCP / TTFB | 首次内容绘制 / 首字节时间 | CDN、边缘缓存、服务端优化、减少重定向 |
测量工具:Lighthouse、Chrome DevTools Performance、Web Vitals 扩展、web-vitals JS 库。
JD 明确写“能针对 Lighthouse/Core Web Vitals 等指标进行调优优先”。这是中高级前端必备技能,考察候选人是否有实际调优经验。
前端工程化
Webpack 与 Vite 的核心差异是什么?为什么 Vite 冷启动更快?
核心差异:
- Webpack:基于 bundler,开发时也会打包所有模块;启动时间和项目规模正相关;生态成熟,配置灵活。
- Vite:基于原生 ESM 的 dev server,开发时按需编译浏览器请求的模块;生产环境用 Rollup 打包。
Vite 冷启动更快的原因:
- 不需要预先打包所有依赖,启动时只处理入口 HTML 和少量预构建(optimize deps)。
- 利用浏览器原生 ESM 解析依赖图,按需加载。
- 编译器使用 esbuild(C++ 编写),比 Webpack 的 JavaScript 解析器快 10-100 倍。
- HMR 按模块边界更新,不需要重新打包整个 bundle。
JD 要求“熟悉前端工程化工具链(如 Vite、Webpack、ESLint、Prettier、Babel 等)”。本题是工具链基础但高频,考察候选人是否理解构建原理。
如何设计一套前端代码规范与提交规范?请说明 ESLint、Prettier、Husky、lint-staged 的分工。
| 工具 | 职责 |
|---|---|
| ESLint | 代码质量与潜在错误检查(unused vars、复杂逻辑、React Hooks 规则、import 顺序等) |
| Prettier | 代码格式化(换行、引号、缩进等),保证风格一致 |
| Husky | Git hooks 管理工具,在 pre-commit 等阶段触发脚本 |
| lint-staged | 只对暂存区文件运行 lint/format,避免全量检查拖慢提交 |
完整流程:
- 项目初始化 ESLint + Prettier,用
@eslint/js、typescript-eslint、eslint-plugin-react-hooks等规则集。 - 配置
.prettierrc与 ESLint 不冲突(关闭 ESLint 中 Prettier 已覆盖的格式规则)。 - Husky 配置
pre-commithook,调用lint-staged。 - lint-staged 配置对暂存文件执行
eslint --fix和prettier --write。 - 可选:使用 commitlint + commitizen 规范提交信息(如 Conventional Commits)。
JD 强调“代码规范意识与文档编写能力”“具备良好的类型设计与工程化规范意识”。工程化规范是团队协作的基石,3-5 年经验者应该能独立搭建规范体系。
前后端协作与 API
RESTful API 与 GraphQL 各有什么优缺点?前端在选型时应考虑哪些因素?
| 维度 | RESTful | GraphQL |
|---|---|---|
| 数据获取 | 多个端点,可能 over-fetch/under-fetch | 单个端点,客户端指定所需字段 |
| 缓存 | HTTP 缓存成熟(CDN、浏览器) | 需要自定义缓存策略(如 Apollo Cache、DataLoader) |
| 学习成本 | 低,符合 HTTP 语义 | 需要学习 Schema、Resolver、Query/Mutation |
| 版本管理 | 常见 v1/v2 版本 | 通过 Schema 演进,避免版本号 |
| 调试工具 | Postman、Swagger | GraphiQL、Playground |
选型因素:团队后端技术栈、数据聚合复杂度、多端需求(移动端需要精简字段)、缓存要求、团队学习成本。BFF(Backend for Frontend)场景 GraphQL 优势明显。
JD 要求“熟悉 RESTful / GraphQL API 调用与集成”。本题考察候选人对两种主流 API 范式的理解及选型能力。
如何设计错误码与统一错误处理机制?请结合 React/Next.js 项目说明。
错误码设计建议:
- 分层:业务错误码(4 位或 6 位) + HTTP 状态码。例如
4001001表示“参数缺失”,4010001表示“登录过期”。 - 约定统一响应结构:
{ code, message, data, requestId }。 - 错误类型分类:客户端错误、鉴权错误、服务端错误、第三方服务错误。
统一错误处理:
- 封装请求库(如基于 axios/fetch 的
request.ts),在响应拦截器中统一解析 code。 - 对需要登录态的接口统一处理 401,跳转登录或刷新 token。
- 对业务错误统一 toast/notification 提示;对 5xx 错误统一展示友好页面。
- Next.js 中可在
error.tsx中捕获路由段错误;React 中可用 Error Boundary 捕获渲染错误。 - 记录 requestId,便于后端排查。
JD 提到“参与接口规范、错误码定义、数据结构等工作”。统一错误处理是前后端协作的关键环节,考察候选人是否具备工程化思维和落地能力。
前端安全
请分别说明 XSS、CSRF、CORS 的原理及前端防御措施。
| 攻击 | 原理 | 前端防御 |
|---|---|---|
| XSS | 攻击者注入恶意脚本,浏览器执行 | 输入过滤、输出转义;React/Vue 默认转义 JSX;使用 DOMPurify 处理富文本;CSP 限制脚本来源 |
| CSRF | 诱导已登录用户访问恶意链接,携带 cookie 发起请求 | SameSite Cookie、CSRF Token、校验 Referer/Origin、敏感操作二次验证 |
| CORS | 浏览器同源策略限制跨域请求 | 前端不处理,由服务端配置 Access-Control-Allow-Origin;开发环境可用代理 |
注意:CORS 不是攻击,而是浏览器的安全机制;真正的防御在服务端配置。
JD 明确写“了解常见的安全机制(XSS、CSRF、CORS 等)”。安全是中高级前端必须掌握的基础,尤其是电商/SaaS/支付类业务。
前端如何安全地存储 Token?Access Token 与 Refresh Token 的存储策略有何不同?
安全存储原则:
- 不推荐 localStorage:易受 XSS 攻击,一旦页面被注入脚本,Token 会被直接读取。
- 推荐 HttpOnly Cookie:由服务端设置
HttpOnly; Secure; SameSite=Strict,JS 无法读取,可防御 XSS 窃取。 - 内存存储:把 Token 保存在内存(如 React Context/Zustand)中,页面刷新后丢失,配合 cookie 中的 Refresh Token 重新获取。
Access Token vs Refresh Token:
- Access Token:有效期短(分钟级),用于接口鉴权,建议内存存储或 HttpOnly Cookie。
- Refresh Token:有效期长(天/周级),用于换取新的 Access Token,必须 HttpOnly Cookie,且建议绑定设备指纹或 rotating refresh token。
JD 提到 SaaS 平台、支付系统、交易类前端经验优先。Token 安全是这些业务的核心,考察候选人是否了解 XSS 风险及现代鉴权最佳实践。
微前端架构
微前端架构解决了什么问题?常见的实现方案有哪些,各有什么特点?
微前端解决的问题:
- 巨石应用难以维护、构建慢、发布耦合。
- 多团队并行开发同一产品时技术栈冲突、发布冲突。
- 希望渐进式重构老系统,而非一次性重写。
常见方案:
| 方案 | 特点 | 适用 |
|---|---|---|
| iframe | 隔离性最强,但体验差(弹窗遮罩、路由同步难) | 临时集成、异构系统 |
| qiankun | 基于 single-spa,JS 沙箱、样式隔离、prefetch,生态成熟 | 国内企业主流方案 |
| Module Federation | Webpack 5 原生,运行时共享依赖,体验接近单体 | 技术栈统一、需要模块共享 |
| Web Components | 标准方案,跨框架,但工程化成本高 | 组件级微前端、跨技术栈 |
JD 明确写“了解微前端架构……现代化前端工程实践者优先”。本题考察候选人对微前端适用场景和主流方案的理解。
在使用 qiankun 或 Module Federation 时,如何处理子应用之间的样式隔离和公共依赖共享?
样式隔离:
- qiankun:开启
strictStyleIsolation: true使用 Shadow DOM,或experimentalStyleIsolation: true给子应用样式加前缀。 - 工程化:CSS Modules、BEM 命名规范、CSS-in-JS(styled-components/emotion)。
- 子应用卸载时清理动态插入的 style 标签。
公共依赖共享:
- qiankun:主应用通过 import-map / externals 注入 React/Vue/Router 等,子应用打包时排除(external)。
- Module Federation:在 ModuleFederationPlugin 中配置
shared,自动去重并共享依赖版本,支持 eager consumption。
注意:公共依赖共享需要统一版本策略,否则容易出现多版本 React 导致的 Hooks 错误。
微前端落地时样式冲突和依赖共享是最常见的问题。本题测试候选人是否有真实踩坑经验,而不仅是概念。
CI/CD 与容器化
一个成熟的前端 CI/CD 流程通常包含哪些阶段?请结合 GitHub Actions / GitLab CI 说明。
- Install:安装依赖,缓存 node_modules(如 actions/setup-node + cache)。
- Lint & Format Check:运行 ESLint、Prettier、TypeScript 类型检查,确保代码规范。
- Unit Test:运行 Jest/Vitest 单元测试,生成覆盖率报告。
- Build:构建生产包,检查构建产物大小。
- E2E Test(可选):Playwright/Cypress 端到端测试。
- Deploy:上传到 CDN / 对象存储 / Kubernetes / Vercel / 自有服务器。
- Notification:失败时通知飞书/钉钉/Slack。
关键原则:前置检查(lint/type/test)优先,避免把问题带到线上;构建产物版本化;回滚策略。
JD 要求“了解 CI/CD 自动化流程”。3-5 年经验者应能描述完整流水线,体现工程化成熟度。
如何为 Next.js / React 项目构建一个轻量的 Docker 镜像?请给出关键优化点。
关键优化点:
- 多阶段构建(Multi-stage):第一阶段用 node 镜像构建,第二阶段用精简镜像(如 node:alpine 或 distroless)运行。
- 依赖分层缓存:先复制 package.json / lockfile 安装依赖,再复制源码,利用 Docker layer cache。
- 使用 .dockerignore:排除 node_modules、.git、.next 等,减少 build context。
- Next.js standalone 输出:配置
output: 'standalone',只打包运行所需的最小文件。 - 非 root 用户运行:创建专用用户提升安全性。
# 示例:Next.js standalone Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
JD 写“Docker/容器化部署等现代化前端工程实践者优先”。本题考察候选人是否具备前端部署与镜像优化的实战经验。
监控与 A/B 实验
前端监控体系应采集哪些指标?如何设计一套简单的 A/B 实验方案?
前端监控指标分类:
- 性能:FCP、LCP、CLS、INP、TTFB、资源加载耗时、API 响应时间。
- 错误:JS 报错、Promise 未捕获异常、资源加载失败、接口错误码。
- 业务:PV/UV、页面停留时长、转化率、按钮点击率。
- 用户行为:点击热图、路由跳转、报错回放(Session Replay)。
A/B 实验设计:
- 分流:基于用户唯一标识(userId/deviceId)做哈希分桶,保证同一用户稳定命中同一版本。
- 埋点:在实验组和对照组分别上报曝光、点击、转化事件,携带 experiment_id + bucket。
- 指标:预先定义核心指标(如转化率)和护栏指标(如错误率、加载时长)。
- 统计:收集足够样本量后做假设检验,判断实验是否显著。
- 灰度:先小流量开启,观察无异常后再全量。
JD 提到“前端监控与 A/B 实验体系”。这属于中高级前端向工程化/数据化延伸的能力,考察候选人是否有数据驱动意识。
加分项专题
Ant Design、Tailwind CSS、shadcn/ui 各有怎样的设计哲学和适用场景?
| 框架 | 设计哲学 | 适用场景 |
|---|---|---|
| Ant Design | 企业级设计语言,组件丰富,生态成熟 | 后台管理系统、SaaS、中后台 |
| Tailwind CSS | Utility-first,原子类直接组合,高度可定制 | 需要快速迭代、强品牌定制的项目 |
| shadcn/ui | 可复制的组件代码,基于 Radix + Tailwind,不依赖 npm 包 | 追求可维护、可定制、现代化体验的项目 |
选型建议:中后台求稳选 Ant Design;追求定制与开发效率选 Tailwind;希望组件代码完全可控、避免黑盒选 shadcn/ui。
JD 加分项写“熟悉 UI 框架并具备良好的设计审美”。本题帮助识别候选人对现代 UI 方案的理解,以及是否能根据项目气质选型。
在 Next.js 项目中如何实现国际化(i18n)?请比较 next-intl 与原生 next-i18next 的差异。
Next.js 国际化常见做法:
- App Router 推荐
next-intl,通过 middleware 根据 locale 路由,Server Components 直接获取翻译。 - Pages Router 常用
next-i18next,基于 react-i18next,在 getServerSideProps/getStaticProps 中加载翻译。
| 维度 | next-intl | next-i18next |
|---|---|---|
| 路由 | App Router + middleware | Pages Router |
| Server Components | 原生支持 | 不支持,需 Client Components |
| API 风格 | 简洁,useTranslations / getTranslations | useTranslation / serverSideTranslations |
| 复数/插值 | 内置 ICU MessageFormat | 基于 i18next 插件 |
实现要点:locale 路由(如 /en/products)、语言切换器、fallback locale、翻译文件按页面/模块拆分。
JD 加分项明确写“国际化项目开发经验(如 i18n、next-intl)”。本题测试候选人是否有真实落地经验,尤其是 App Router 下的 next-intl。
Node.js 中间层在大型前端项目中能起到什么作用?你如何看待“全栈思维”在前端架构中的价值?
Node.js 中间层(BFF)作用:
- 数据聚合:合并多个后端接口,为前端提供统一视图,减少请求次数。
- 协议转换:把 RPC/GraphQL/第三方接口转成前端友好的 REST。
- 渲染层:Next.js / Nuxt 的 SSR 服务,承担首屏渲染和 SEO。
- 鉴权与代理:统一处理登录态、权限校验、敏感信息脱敏。
- 性能优化:缓存、接口合并、SSR 流式输出。
全栈思维价值:
- 前端能更合理地设计接口契约、错误码、数据结构和缓存策略。
- 减少前后端往返沟通成本,提升交付效率。
- 在架构层面做端到端优化,而不是只局限在浏览器端。
边界:全栈不等于一个人做所有事,而是在理解后端、运维、产品的基础上做出更优的前端技术决策。
JD 加分项写“了解 Node.js 或 Golang 等后端开发,具备一定的全栈思维”。本题考察候选人是否有跨端视角,是否能把前端架构放到整个技术栈中思考。