Skip to content

React 与 Vue ​

1. 说说 Hooks 的依赖数组原理 ​

答案:

Hooks 的依赖数组用于声明副作用或缓存值所读取的响应式输入。React 会在每次渲染后比较本次与上次依赖项,通常使用 Object.is;任一项变化才重新执行 effect 或重新计算 memo。空数组表示只依赖首次挂载时的值,但闭包可能捕获旧数据;省略数组表示每次渲染都执行。依赖应完整列出组件作用域中使用的外部值,不能靠手工删依赖掩盖闭包问题。

2. 为何setup()中直接解构props会丢失响应性? ​

答案:

props 是响应式代理,直接解构会触发一次属性读取,把当前值复制到普通变量;后续父组件更新 props 时,这个变量没有自己的依赖追踪,因此不再响应。Vue 3 可用 toRefs(props) 将每个属性转成 ref,或用 toRef(props, 'name') 保留单项响应性;也可直接使用 props.name。模板中的 props 解构要遵守同样原则。

3. Vue 中,多个组件复用同一响应式对象时,如何避免不必要的重新渲染? ​

答案:

先判断共享对象是否真的需要全量响应。可把稳定数据用 readonly 或 shallowReadonly 暴露,避免子组件意外修改;对大对象使用 shallowReactive,只让根属性触发更新。组件内部再通过 computed、toRef 或按字段拆分状态,只订阅实际需要的属性,避免把整个对象作为依赖。列表还应保持稳定的 key,必要时配合 v-memo,而不是盲目缓存。

4. Vue2中,为什么在beforeCreate阶段,无法访问data? ​

答案:

Vue 2 的 beforeCreate 是实例初始化早期钩子,此时只完成了事件和生命周期等基础初始化,data、props、computed、methods 还没有被注入实例并建立响应式代理。因此访问 this.dataField 得不到已初始化的数据,通常应在 created 中访问;如果还需要操作已生成的 DOM,则要等到 mounted。

5. setup函数中同步修改响应式数据,会触发子组件的onBeforeUpdate吗?为什么? ​

答案:

通常不会因为 setup() 中的同步赋值就触发子组件的 onBeforeUpdate。setup 执行于当前组件首次渲染前,子组件尚未完成挂载;同步修改会被合并到初始渲染状态,子组件只经历首次挂载钩子。只有响应式数据在挂载完成后再次变化,并且确实导致该子组件更新时,子组件才会进入 onBeforeUpdate。若修改发生在异步回调中,则可能触发更新。

6. Vue3 中,动态绑定v-bind:class="{ active: isActive }"会被编译成什么样的JS代码? ​

答案:

编译器会把它转换成带 class patch 标记的 vnode 代码,核心形态近似:_createElementVNode('div', { class: _normalizeClass({ active: _ctx.isActive }) }, null, 2 /* CLASS */)。实际生成函数名可能是 _createElementVNode 或 _createElementBlock,取决于上下文。_normalizeClass 负责把对象 class 转成字符串;isActive 被标记为动态依赖,变化时只更新 class,而不是重建整棵 DOM。

7. Vue的Diff为何采用同层比较,而非跨层? ​

答案:

Vue 的 Diff 采用同层比较,是在准确性和复杂度之间的工程取舍。跨层移动节点的可能组合数量很大,需要维护复杂的树编辑算法,时间和内存成本都高;实际 UI 中跨层移动并不常见。Vue 先比较同一父节点下的子节点,再通过 key 复用、移动或新增节点,通常可将列表更新控制在近似线性范围。跨层结构变化一般按卸载旧节点、挂载新节点处理。

8. Vue.use 如何注册插件 ​

答案:

Vue 2 的 Vue.use(plugin, ...args) 会先检查插件是否已安装,利用插件对象或函数本身做去重;未安装时把它记录到内部列表,再调用 plugin.install(Vue, ...args),如果插件本身是函数则直接调用 plugin(Vue, ...args)。插件通常在 install 中注册全局组件、指令、过滤器,或通过 Vue.prototype 注入实例能力。重复调用同一插件不会再次安装。

9. Vue3和Vue2的响应式原理有什么差别? ​

答案:

Vue 2 主要用 Object.defineProperty 劫持已存在属性的 getter/setter,初始化时递归观测对象;新增属性和数组下标、长度变化无法自动拦截,需 $set 等补充。Vue 3 使用 Proxy 代理对象,能统一拦截属性新增、删除、数组操作和 in 等行为,并通过 Reflect 保持默认语义。Vue 3 还用 ref 包装基本类型;两者都通过依赖收集和触发更新,但 Vue 3 的拦截覆盖面和初始化成本更好。

10. 假设你正在开发一个 Vue 项目,需要在组件加载完成后执行一个数据初始化操作,并在组件销毁前清理一些资源(如定时器或事件监听)。你会使用哪些生命周期钩子来实现这些功能,并简述每个钩子的作用。 ​

答案:

Vue 3 中,组件加载完成后做初始化通常使用 onMounted,此时模板 DOM 已插入,可以发起请求、读取元素或启动定时器。组件销毁前使用 onBeforeUnmount 清理事件监听、定时器、订阅和外部实例;若必须在卸载完成后处理,也可用 onUnmounted。Vue 2 对应的是 mounted、beforeDestroy 和 destroyed。清理逻辑应与创建逻辑成对出现,避免资源泄漏。

11. 在Vue项目中,如何实现全局状态管理? ​

答案:

跨多个页面和无直接父子关系的状态,通常用 Pinia(Vue 3 首选)或 Vuex 建立 store,集中定义 state、getters 和 actions,再在组件中按需读取和修改。小规模场景可用 provide/inject 共享状态,父子组件用 props 与 emits,兄弟组件通过共同父级转发。选择原则是先保持数据流清晰,只有共享范围、持久化、调试或异步协调复杂时才引入全局 store。

12. Vue路由的原理是什么?请简述Vue Router如何实现页面跳转和组件的动态加载。 ​

答案:

Vue Router 监听 URL 与路由表的匹配结果,把当前路由映射为组件树并交给 Vue 渲染。History 模式用 pushState、replaceState 修改地址并监听 popstate;Hash 模式监听 hashchange,避免服务端配置要求。导航时先解析路径、执行守卫,再更新路由状态;路由组件可通过 component: () => import('./Page.vue') 动态导入,打包器会拆成异步 chunk,首次匹配时按需加载。

13. React 的虚拟 DOM 解决了什么问题? ​

答案:

虚拟 DOM 用 JavaScript 对象描述 UI,使组件渲染先得到可比较的树,再由 Diff 计算最小必要的真实 DOM 操作。它解决了手写 DOM 更新容易遗漏、状态与视图同步困难、跨平台渲染难复用等问题,并提供统一的声明式编程模型。但它不是“比 DOM 天然更快”:简单场景手写 DOM 可能更快,性能收益来自批量更新、节点复用和减少无效操作。

14. Vue3 中的 Teleport 是什么,有哪些应用场景? ​

答案:

Teleport 是 Vue 3 的内置组件,允许逻辑上属于当前组件的内容,实际渲染到 DOM 中指定的其他容器,例如 body 或全局弹层根节点。它只改变 DOM 位置,不改变组件的响应式关系、事件处理和生命周期。常用于模态框、下拉菜单、通知、全屏面板等,避免父级 overflow、层叠上下文或 z-index 造成的裁剪;目标容器不存在时要处理挂载时机。

15. 为什么需要Pinia或Vuex?如果不使用状态管理工具,如何实现跨组件通信?替代方案有哪些? ​

答案:

Pinia 或 Vuex 解决的是跨层共享状态的统一读写、响应式更新、调试和持久化问题,避免层层传参与事件转发。无需状态库时,父子组件用 props/emits,祖孙组件可用 provide/inject,兄弟组件可提升状态到共同父组件;还可用 composable、URL 参数、浏览器存储或事件总线。事件总线适合短生命周期通知,不宜承载复杂、可变的业务状态。

16. Vue3的addRoute与React Router的动态useRoutes有何区别?如何解决动态路由刷新后失效的问题? ​

答案:

Vue 3 的 router.addRoute 直接把路由记录加入 Router 实例;React Router 的 useRoutes 则是在渲染阶段根据配置生成匹配元素,动态配置变化要通过状态重新渲染。动态路由刷新失效的根因是内存配置不会持久化。登录后应从后端获取菜单,校验并生成路由,先完成 addRoute 或更新 routes 状态,再重定向到目标地址;刷新时在路由初始化阶段从 token、缓存或接口恢复,避免先进入 404。

17. Next.js 中的 AppRouter 和 PageRouter 有什么区别? ​

答案:

Next.js 的 App Router 基于 app 目录,使用 React Server Components、嵌套路由布局、流式渲染、Suspense 和更细粒度的缓存;默认组件是服务端组件,需要交互时加 use client。Page Router 基于 pages 目录,采用传统 SSR/SSG、API Routes 和 getServerSideProps 等模型,生态和迁移兼容性更成熟。两者可阶段性共存,但同一 URL 不能同时由两套路由处理。

18. Vue2中为什么需要$set方法?如何用它解决对象新增属性的响应性问题? ​

答案:

Vue 2 初始化时只为已有属性建立 defineProperty 的 getter/setter,运行中直接新增对象属性没有被拦截,因此不会触发依赖更新。this.$set(target, key, value) 会在目标是响应式对象时调用内部 defineReactive 建立访问器,并通知依赖;数组则会通过变异方法处理。根实例或非响应式对象不能靠 $set 变成响应式,修改前应优先在 data 中声明完整结构。

19. Vue3中v-model的改进是什么?如何用defineModel简化代码? ​

答案:

Vue 3 的组件 v-model 默认约定从 Vue 2 的 value/input 改为 modelValue/update:modelValue,并支持多个命名模型,如 v-model:title,参数和修饰符也更统一。Vue 3.4+ 可用 defineModel():const model = defineModel() 得到可读写 ref,模板直接绑定它即可,编译器会生成 prop 与 emit;也可传名称和默认值。它不能替代对外部输入的校验,且默认值要防止父子初始值不同步。

20. Vue2中v-model的实现原理 ​

答案:

Vue 2 会把组件上的 v-model 编译成一个 prop 和一个事件监听。普通组件默认等价于 :value="x" @input="x = $event",子组件通过 $emit('input', newValue) 回传新值;组件可用 model 选项改成其他 prop 和事件。原生元素则根据标签映射到 value/input、checked/change 等属性事件。它本质是语法糖,不是双向绑定的魔法。

21. 在Vue中,为什么推荐使用ref而非直接操作DOM? ​

答案:

模板 ref 让 Vue 在渲染完成后提供元素或子组件实例引用,适合聚焦输入框、测量尺寸、调用第三方 DOM 库等必须接触命令式 API 的场景。直接 querySelector 会绕过组件边界、生命周期和条件渲染管理,容易拿到过期节点,也可能产生选择器冲突。ref 只能在挂载后可靠读取,卸载或 v-if 切换时可能为 null;常规内容仍应通过响应式状态驱动。

22. 在 React 中,useEffect 钩子的作用是什么?它的参数有哪些?如何使用 useEffect 实现组件的副作用处理? ​

答案:

useEffect 用于处理渲染之外的副作用,如请求、订阅、定时器、手动同步 DOM。参数依次是副作用函数和依赖数组;函数可返回清理函数,依赖变化或组件卸载前执行清理。省略依赖表示每次提交后执行,[] 表示只在挂载后执行一次(但闭包仍捕获初始值),指定依赖则在依赖变化后执行。副作用应可重复执行,避免把派生计算塞进 effect。

23. 如何实现一个可以监听任何事件的React Hooks? ​

答案:

可以封装 useEventListener,让目标、事件名和处理函数变化时正确绑定,并在卸载时清理。处理函数用 ref 保存,避免每次渲染重复 add/remove,也能拿到最新闭包:

tsx
function useEventListener<K extends keyof WindowEventMap>(type: K, fn: (e: WindowEventMap[K]) => void, target: Window = window) {
  const latest = React.useRef(fn);
  React.useEffect(() => { latest.current = fn; }, [fn]);
  React.useEffect(() => {
    const handler = (e: WindowEventMap[K]) => latest.current(e);
    target.addEventListener(type, handler as EventListener);
    return () => target.removeEventListener(type, handler as EventListener);
  }, [type, target]);
}

复杂度为 O(1) 监听资源;SSR 时应避免直接在渲染阶段读取 window。

24. vue3 中 ref 和 reactive 有什么区别? ​

答案:

ref 是响应式容器,以 .value 作为依赖单位,能包装基本类型,也支持整体替换对象;模板中通常会自动解包。reactive 返回对象代理,直接追踪属性访问,适合结构化状态,但不能安全整体替换,直接解构属性也会丢失响应性,需用 toRef/toRefs。实践中基本值、可能整体替换的数据用 ref,稳定的表单或对象状态用 reactive;两者也可以组合使用。

25. 在 React 项目中,一般怎么处理错误? ​

答案:

错误处理要分层:请求和业务错误在调用处用 try/catch、统一拦截器和可恢复的 UI 状态处理;渲染期间、生命周期和子树事件中的未捕获错误用 Error Boundary 展示降级界面并上报。边界组件可记录错误信息和组件栈,但不能捕获事件处理器、异步回调或服务端渲染错误,这些要在各自入口处理。生产环境还应监听全局 error、unhandledrejection,并避免向用户暴露敏感堆栈。

26. 在 React 中如何实现代码分割?有哪些常见方式? ​

答案:

常见代码分割方式是使用动态 import(),让打包器拆出异步 chunk;路由页面用路由懒加载,组件用 React.lazy(() => import('./Panel')),外层配合 Suspense 提供加载状态。对不稳定或很少使用的功能也可按交互触发导入。Webpack/Vite 会结合预加载、缓存和 chunk 策略优化请求。代码分割会增加请求与加载态复杂度,应按页面边界拆分,避免过度碎片化。

27. template 标签为什么不可以使用 v-show? ​

答案:

v-show 的实现是给一个真实 DOM 元素切换 display 样式;template 只是编译期的无渲染容器,本身不会生成 DOM 节点,因此没有可切换的 display 目标,不能使用 v-show。需要控制一组节点是否存在时使用 template v-if,需要频繁显示隐藏且保留节点时,把 v-show 放到实际元素或组件上。

28. 如何通过 React.memo 优化条件渲染? ​

答案:

React.memo(Component) 会对父组件传入的 props 做浅比较;当父组件因条件状态变化而重新渲染时,如果子组件 props 引用和值都未变,React 可跳过该子组件的重新渲染。它只优化组件自身,context、内部 state 变化仍会触发更新;对象、数组和函数若每次创建新引用也会失效,需要配合 useMemo/useCallback。自定义比较函数必须准确且便宜,不能把条件渲染写成依赖 memo 的逻辑保证。

29. watch与watchEffect 有什么区别,分别在什么场景下使用? ​

答案:

watch 需要明确指定 source,可监听 ref、getter 或数组,回调能拿到新旧值,适合精确监听、异步请求、需要旧值或控制 deep、immediate、flush 的场景。watchEffect 会立即执行,并自动收集执行过程中同步访问的响应式依赖,适合依赖关系简单、希望自动同步的副作用;没有旧值,异步回调中首次 await 后访问的依赖也不会被追踪。复杂监听优先用 watch。

30. Pinia 有哪些使用场景? ​

答案:

Pinia 适合多个无直接父子关系的组件共享状态,例如用户登录信息、权限、购物车、主题配置、跨页面筛选条件和缓存数据。它也适合集中封装异步请求、派生 getters 与可复用业务动作,便于 DevTools 调试和持久化扩展。局部状态或一次性的父子通信不必放进 Pinia;状态范围过大、模块边界不清会增加耦合,应按业务域拆分 store。

31. 说说 Pinia 与 Vuex 的区别 ​

答案:

Pinia 是 Vue 官方推荐的新一代状态库,API 更简洁:没有 mutations,直接在 actions 或组件中修改 state;store 可按模块自然拆分,支持完整 TypeScript 推导和组合式写法。Vuex 采用集中式 store、mutations 与 actions,约束更强,适合维护历史 Vue 2 项目,但样板代码较多。Pinia 默认支持多个 store、插件和 DevTools;选择还要考虑项目版本、团队习惯与既有生态。

32. 说说你对 Vue 中异步组件的理解 ​

答案:

Vue 异步组件把组件代码拆成按需加载的异步模块,首次渲染到它时才执行动态 import,适合路由页面、弹窗和低频功能,能减小首屏包体。Vue 3 通常这样定义:

ts
const Panel = defineAsyncComponent({
  loader: () => import('./Panel.vue'),
  loadingComponent: Loading,
  errorComponent: Failed,
  delay: 200,
  timeout: 10000
})

它会经历加载、成功或失败状态,可配合 Suspense。要权衡额外请求、加载反馈、缓存和 SSR,不能把所有组件都拆成异步模块。

33. 怎么理解 Vue3 提供的 markRaw ? ​

答案:

markRaw 用来明确告诉 Vue:不要把指定对象转换为响应式代理。它返回原对象,适合第三方类实例、组件定义、图表实例等不应被追踪或不便代理的值。代价是对象内部变化不会触发更新;若把它放进响应式对象,读取时仍保持原引用。

34. Vue 中的 h 函数有什么用? ​

答案:

h 是 Vue 的虚拟节点创建函数,完整形式是 h(type, propsOrChildren, children),可在 render 函数或 JSX 中描述组件、元素及其属性和子节点。它最终生成 VNode,交给 Vue 的 patch 流程更新真实 DOM;复杂动态结构或渲染函数场景会直接使用它。

35. redux-thunk 和 redux 是什么关系? ​

答案:

Redux 是状态管理核心:提供 store、reducer、dispatch 和订阅机制,dispatch 原则上只接受同步普通 action。redux-thunk 是中间件,扩展 dispatch,使 action 可以是函数;函数中可异步请求,再 dispatch 普通 action。它不是 Redux 的替代品,而是建立在 middleware 机制上的异步方案。

36. React Router 中,HashRouter 和 BrowserRouter 的区别和原理? ​

答案:

HashRouter 把路由放在 URL 的 # 后,通过监听 hashchange 切换,浏览器不会把 hash 发送给服务器,部署简单但 URL 不够美观。BrowserRouter 使用 History API 的 pushState、replaceState 和 popstate 管理真实路径,更适合生产 URL,但刷新非首页时服务器必须把请求回退到应用入口,否则会 404。

37. React中,useRef、ref、forwardsRef 的区别是什么? ​

答案:

useRef 是函数组件中保存跨渲染可变值的 Hook,返回稳定对象,修改 current 不触发渲染。JSX 的 ref 是把引用交给 DOM 或组件的属性;函数组件本身没有实例,普通函数组件不能直接接收 ref。forwardRef 可让函数组件接收第二个 ref 参数并转发给内部 DOM 或子组件,必要时再配合 useImperativeHandle 暴露有限方法。

38. useEffect的第二个参数,是如何判断实现依赖是否发生变化的? ​

答案:

React 在更新时按依赖数组下标逐项比较,使用 Object.is,不是深比较,也不是普通 ===。首次渲染必执行;后续只要长度对应项存在任一不相等就执行 effect,否则跳过。对象、数组、函数每次新建都会被视为变化,因此应稳定引用或准确拆出原始依赖;省略第二参数表示每次渲染后执行,空数组表示仅首次提交后执行一次(开发 Strict Mode 可能额外模拟执行)。

39. 手写 vue 的双向绑定 ​

答案:

Vue 2 风格的双向绑定可用 Object.defineProperty 拦截数据、发布订阅通知视图,再监听输入事件回写模型:

js
function bind(model, key, input) {
  const subs = []; let value = model[key];
  Object.defineProperty(model, key, { get: () => value, set(v) { value = v; subs.forEach(fn => fn(v)); } });
  const render = v => { input.value = v ?? ''; }; subs.push(render); render(value);
  input.addEventListener('input', e => { model[key] = e.target.value; });
}

核心是 getter 收集依赖、setter 派发更新;该示例只处理单字段,数组新增属性和删除属性需额外 API。

40. vue2 中的虚拟 dom 是怎么实现的? ​

答案:

Vue 2 的虚拟 DOM 由模板编译器把模板转成 render 函数,运行时执行函数生成 VNode 树。VNode 描述标签、属性、文本、组件和子节点,不是真实 DOM。数据变化触发组件 watcher 重新执行 render,产生新旧 VNode;patch 通过同层比较、key 匹配、属性更新和子节点增删移动,把最小必要变化同步到真实 DOM。

41. Vue 是如何实现 MVVM 的? ​

答案:

Vue 的 MVVM 可理解为:Model 是响应式状态,View 是模板编译出的 DOM,ViewModel 是 Vue 实例及其响应式、渲染机制。状态读取时建立依赖,状态写入时通知 watcher/组件重新渲染;输入事件则通过 v-model 的事件处理器修改状态。这样业务通常只操作 Model,不直接查询和修改 DOM,但 Vue 本身并非严格教科书式 MVVM。

42. Proxy 和 Object.defineProperty 的区别是啥? ​

答案:

Object.defineProperty 是按属性拦截 getter/setter,Vue 2 需要递归遍历初始化,新增属性、删除属性和部分数组索引变化无法自动侦测,需 Vue.set 等补充。Proxy 拦截整个对象的读取、写入、删除、in 等操作,惰性代理,能覆盖新增键和数组变化,性能与表达力更好;但不能代理私有原始值,且旧环境不支持时需要兼容方案。

43. Vue 的响应式数据流驱动页面和传统的事件绑定命令式驱动页面,分别有什么优缺点? ​

答案:

响应式数据流是声明式的,状态变化后框架负责更新相关视图,代码更聚焦数据、组件复用和维护,适合复杂交互;代价是需要理解依赖追踪、更新时机,调试调用链有时不直观。传统命令式事件绑定由开发者精确控制每一步,简单页面和局部高频操作可更直接、开销可控,但状态与 DOM 容易分散,规模增大后同步、复用和维护成本明显上升。

44. useState 是如何实现的? ​

答案:

useState 可抽象成每个组件 Fiber 持有按调用顺序排列的 Hook 链表。首次渲染创建 Hook 并保存初始值,返回值和稳定的 dispatch;dispatch 把 action 放入该 Hook 的更新队列,标记 Fiber 需要更新并调度渲染。下一次 render 按相同顺序遍历队列,依次执行值或函数 action 得到新状态,再参与 reconciliation。

js
function reducer(state, action) { return typeof action === 'function' ? action(state) : action; }

因此 Hook 不能放在条件分支;多个更新会批量处理,函数式更新能避免闭包旧值。

45. vue 文件中,在 v-for 时给每项元素绑定事件需要用事件代理吗,为什么? ​

答案:

通常不需要开发者额外做事件代理。Vue 会把模板中的 @click 编译成事件监听逻辑,并通过事件缓存、patch 和必要的复用降低成本;但是否一个监听器对应每个节点取决于版本和编译结果。列表特别大、节点频繁增删或事件逻辑适合统一处理时,可以手写父元素代理,再用 event.target.closest 判断目标,同时注意 stopPropagation、动态节点和可访问性边界。

46. vue3 的响应式库是独立出来的,如果单独使用是什么样的效果? ​

答案:

Vue 3 的响应式能力在 @vue/reactivity 包中独立提供,可脱离组件使用:reactive/ref 创建状态,computed 派生值,effect 在依赖变化时重新执行。

js
import { ref, effect } from '@vue/reactivity';
const count = ref(0); effect(() => console.log(count.value)); count.value++;

输出初始值 0,随后输出 1。它只负责依赖追踪,不负责 DOM、组件生命周期或模板渲染;与 Vue runtime 配合时才形成完整应用。

47. Vue 中的双向绑定和单向数据流原则是否冲突? ​

答案:

不冲突。Vue 的单向数据流强调父组件通过 props 向下传值,子组件不能直接改父级 prop,而应通过事件通知父级更新。v-model 的“双向”只是语法糖:组件上通常展开为 :modelValue='value' 和 @update:modelValue='value = $event',数据本质仍沿单向输入、事件回传的路径流动。原生表单元素则由指令封装了这套同步。

48. Vue 2 的响应式原理中 Object.defineProperty 有什么缺陷 ​

答案:

它只能拦截初始化时已经存在的属性,因此后加的对象属性不会自动响应,删除属性也无法被侦测;数组通过索引赋值或直接改长度同样难以拦截,需 Vue.set、splice 等补充方式。初始化需要递归遍历并为每个属性建立 getter/setter,嵌套对象成本较高;另外属性描述符和某些不可配置对象也限制了改造。

49. forwardRef 作用是什么? ​

答案:

forwardRef 把父组件传入的 ref 转交给函数组件内部的 DOM 或子组件,使父组件能执行聚焦、测量等必要的命令式操作。它接收 (props, ref) 并返回 JSX,例如 const Input = React.forwardRef((props, ref) => <input ref={ref} {...props} />)。它不会自动创建实例,也不应暴露内部全部细节;可配合 useImperativeHandle 定义稳定、有限的命令接口。

50. 怎么获取函数组件的实例? ​

答案:

函数组件没有类似类组件的实例对象,因此不能直接获取“组件实例”。实际需求通常是用 forwardRef 接收 ref,再用 useImperativeHandle 暴露有限的命令方法,或把状态和行为提升到父组件,通过 props/回调控制。若只是访问节点,直接把 ref 转发到内部 DOM;不要依赖函数组件的内部闭包或非公开 Fiber。

51. React Reconciler 为何要采用 fiber 架构? ​

答案:

旧版 React 的递归协调可能长时间占用主线程,复杂更新会阻塞输入和动画,且中途不能暂停。Fiber 把组件树拆成可恢复的工作单元,并为每项工作保存状态、父子兄弟链接和副作用信息;render 阶段可按优先级、时间预算让出主线程,完成后再统一 commit。这样支持可中断、可恢复、可丢弃的并发调度,但 commit 仍必须保持一致、不可被随意打断。

52. fiber 架构的工作原理? ​

答案:

更新开始后,React Scheduler 选择优先级任务,协调器从根 Fiber 开始执行 beginWork,比较 props/type 并生成或复用子 Fiber;再通过 completeWork 向上归并 DOM 信息和副作用,形成 work-in-progress 树。render 阶段可以在空闲时间切片、暂停或重做;整棵树准备好后进入 commit,一次性执行 DOM 变更、ref 和 effect。

53. 说说Fiber的含义与数据结构 ​

答案:

Fiber 既是 React 的工作架构,也是描述组件、DOM 或文本节点的对象。常见字段有 tag、type、key、pendingProps、memoizedProps、memoizedState、stateNode,以及 return、child、sibling 形成树状链表;alternate 指向另一棵 current/work-in-progress 树。它还保存 flags、lanes 等副作用和优先级信息,支持暂停、恢复与双缓存提交。

54. 说说 React render 阶段的执行过程 ​

答案:

render 阶段从根 Fiber 开始构建 work-in-progress 树。beginWork 根据组件类型、props 和更新队列执行函数组件或类组件,计算子节点并协调子 Fiber;completeWork 自下而上处理 Host 节点信息,合并子树 flags 和需要提交的副作用。该阶段主要计算目标状态,不应产生用户可见副作用,而且在并发模式下可以暂停、重试或丢弃。

55. 说说 React commit 阶段的执行过程 ​

答案:

commit 阶段接收 render 阶段完成的 Fiber 树,通常分为三个环节:before mutation 读取变更前布局信息并处理快照;mutation 执行 DOM 插入、更新、删除以及 ref 清理;layout 更新 ref 并同步执行 useLayoutEffect。提交完成后浏览器绘制,React 再异步执行 useEffect。该阶段必须保持一致,通常不可被中断。

56. React 中,fiber 是如何实现时间切片的? ​

答案:

Fiber 的时间切片依赖“一个 Fiber 一个工作单元”。协调器每完成一个单元就检查调度器提供的时间预算,若时间耗尽则保存当前 Fiber 的位置并让出主线程,通过 MessageChannel 等机制稍后继续;高优先级输入可插队。render 结果保存在 work-in-progress 树中,只有完整树进入 commit 才修改 DOM,因此中断或重做不会暴露半成品。

57. 说说你对 createPortal 的了解 ​

答案:

createPortal(children, domNode) 把 React 子树渲染到当前 DOM 层级之外的指定容器,例如把弹窗挂到 body。它改变物理 DOM 位置,但仍属于原 React 树:context 可继续读取,事件冒泡按 React 树传播,而不是按 DOM 祖先传播。常用于 modal、tooltip、下拉层,需自行处理层级、焦点管理、服务端容器存在性和卸载清理。

58. React 中的 createContext 和 useContext 分别有什么用? ​

答案:

createContext(defaultValue) 创建一个 Context 对象,默认值只在组件没有匹配 Provider 时使用;Provider 通过 value 向后代提供共享数据。useContext(Context) 在函数组件中读取最近 Provider 的值,Provider 的值变化会让消费组件重新渲染,即使中间组件没有传 props。它适合主题、语言、认证信息等低频广域依赖,不宜把高频且无关的大对象塞进单一 Context。

59. 使用 react-router 跳转时,如何将参数传递给下一个页面? ​

答案:

常见传参方式有三类:路径参数,如 navigate('/users/' + id),在路由 /users/:id 中用 useParams() 读取;查询参数,如 navigate('/list?page=2'),用 useSearchParams() 读取;非持久的导航状态,如 navigate('/detail', { state: { id } }),目标页用 useLocation() 读取。路径和 query 可复制、刷新保留,state 依赖当前历史记录,刷新或直接打开 URL 时不能把它当唯一数据来源。

60. vue 是如何识别和解析指令的? ​

答案:

Vue 模板解析时先把元素属性按语法分类:普通属性进入 props/attrs,v- 开头的内容识别为指令,解析指令名、参数、修饰符和表达式,例如 v-on:click.stop 会得到事件指令、click 参数和 stop 修饰符。编译器把它们转换为 render 函数中的指令调用或事件处理代码;运行时由指令钩子在绑定、更新、卸载阶段执行。自定义指令也遵循这套生命周期。

61. vue3 相比较于 vue2,在编译阶段有哪些改进? ​

答案:

Vue 3 编译器会对模板做静态提升,把不依赖响应式状态的 VNode/属性移出 render,减少重复创建;对动态节点标记 patch flag,使运行时只检查可能变化的部分;通过 block tree 收集动态后代,缩小 diff 范围;并对静态文本、事件处理器等做缓存。配合更高效的 diff 和编译时树摇,还能降低运行时开销与产物体积。

62. react 中,数组用useState做状态管理的时候,使用push,pop,splice等直接更改数组对象,会引起页面渲染吗? ​

答案:

通常不会可靠触发渲染。push、pop、splice 会原地修改数组,但 setState 的更新判断依赖新旧状态引用;若再传入同一个数组引用,React 可能认为没有变化,且直接变更会破坏不可变更新假设。应创建新引用:setList(prev => [...prev, item])、setList(prev => prev.filter(...)) 或 setList(prev => prev.toSpliced(...))。即使父级因别的原因重渲染,直接修改的内容也可能被偶然显示,不能依赖这种行为。

63. react 中,在什么场景下需要使用 useContext? ​

答案:

当一份稳定的、跨多层组件共享的数据不适合逐层 props 传递时使用 useContext,例如主题、语言、当前用户、权限或表单上下文。它减少 prop drilling,但消费组件会在 Provider 的 value 引用变化时更新,因此应拆分 Context、稳定 value,并避免把高频变化的全部状态放进去。局部状态、父子间少量数据优先用 props 和回调,复杂状态可考虑专门状态库。

64. Vue 项目中,你做过哪些性能优化? ​

答案:

我会从测量结果出发优化:路由和大型组件按需加载,合理使用缓存与 CDN,压缩图片并采用懒加载;模板中减少不必要的深层响应式数据,使用 computed、稳定 key 和分页/虚拟列表;拆分组件边界,避免无关更新,必要时用 v-once、v-memo;生产构建开启压缩、Tree Shaking 和预渲染/SSR。最后用 Lighthouse、Performance 和包分析确认首屏、交互延迟及内存确实改善,避免盲目加缓存。

65. React 中的 hooks 和 memorizedState 是什么关系? ​

答案:

Hooks 是给函数组件使用的状态与副作用 API;memorizedState(Fiber 上的字段)是 React 保存该组件 Hooks 链表状态的内部容器。每次渲染按调用顺序遍历同一条链表,useState 保存状态和更新队列,useMemo 保存值与依赖,useRef 保存 ref 对象。因此它不是某个 Hook,而是 Hooks 运行时的存储机制;调用顺序改变会导致状态错位。

66. React 中,怎么给 children 添加额外的属性? ​

答案:

使用 React.cloneElement 克隆单个 React 元素并合并属性,例如 React.cloneElement(child, { className: 'active', onClick })。它只适合明确的单一元素,且新 props 会覆盖同名值;多个 children 要先用 React.Children.map 遍历。不要直接修改 children,也不要把普通字符串或数组当作可克隆元素,必要时先用 React.isValidElement 判断。

67. React 中,构建组件的方式有哪些? ​

答案:

常见方式有函数组件、类组件,以及通过高阶组件、render props、Hooks 等组合既有组件逻辑。函数组件配合 Hooks 是现代 React 的主流;类组件仍可维护旧代码,错误边界等场景也常见。组件本质上接收 props、根据状态计算 UI,并通过组合复用能力;不应把所有逻辑都抽成高阶组件,优先使用清晰的函数组件和自定义 Hook。

68. React 是怎么渲染出页面的? ​

答案:

入口调用 createRoot(container).render(<App />) 后,React 创建 Fiber 根节点并执行组件函数,得到 React Element,再构建或更新 Fiber 树。协调阶段比较新旧 Fiber,计算插入、更新、删除等副作用;提交阶段集中修改真实 DOM、处理 ref 和生命周期。更新由 state、props 或 context 触发,React 先批量调度,再通过 diff 尽量复用节点,key 用于识别同级列表项。

69. vue-cli 有哪些功能? ​

答案:

vue-cli 是 Vue 2 时代的脚手架工具,负责创建项目、选择预设和插件,并生成 webpack 开发、构建配置。它提供开发服务器与热更新、生产构建与代码压缩、环境变量、静态资源处理、ESLint、单元测试及 E2E 测试等能力。新项目通常优先使用 Vite 或 create-vue;Vue CLI 仍主要用于维护已有工程。

70. Vue2 动态给 data 添加一个新的属性时会发生什么 ​

答案:

Vue 2 通过 Object.defineProperty 在初始化阶段拦截已存在属性,运行时直接给对象新增属性不会被转换为响应式,因此模板通常不会因它变化而更新。对象应使用 this.$set(obj, key, value) 或 Vue.set,数组可用 splice;若属性可能存在,也可在 data 中预先声明。Vue 3 基于 Proxy,新增普通对象属性可被自动追踪。

71. React 中,如何实现类似于 Vue-router 提供的路由守卫? ​

答案:

React Router 没有 Vue Router 同名的全局守卫,可在路由渲染入口包一层鉴权组件:读取登录状态和目标路径,未登录时用 <Navigate replace to="/login" state={{ from: location }} /> 重定向,已登录才渲染 Outlet。权限、角色、加载状态分别处理;异步校验期间显示 loading,避免先渲染受保护页面。提交前的权限校验仍必须放在服务端。

72. 为什么 react 组件中, 都需要声明 import React from 'react'; ​

答案:

旧版 JSX 会被 Babel 转成 React.createElement(...),所以文件中必须引入 React,否则运行时找不到标识符。React 17 起的 automatic JSX runtime 会改为引入 jsx 等函数,普通 JSX 文件可以不显式 import React;但若直接使用 React.Component、React.Children 等,仍需导入。是否需要取决于 JSX 转换配置和代码实际引用。

73. 在 React 项目中, 是否可以不用 react-router, 使用浏览器原生 history 路由来组织页面路由? ​

答案:

可以。用 history.pushState 或 replaceState 修改地址、监听 popstate 处理前进后退,再根据 location 映射并渲染页面即可。需要补齐路由匹配、参数解析、懒加载、404、滚动恢复、鉴权和错误边界等能力,并配置服务端将任意前端路径回退到 index.html;否则刷新非根路径会 404。复杂项目使用 React Router 可减少维护成本。

74. react-router 和原生路由有什么区别? ​

答案:

原生路由是浏览器的 History 或 Hash API,只负责改变 URL 和通知变化,页面匹配、嵌套路由、参数、导航状态、懒加载与守卫都要自行实现。react-router 是 React 生态的路由层,封装了这些能力,并通过组件、Hooks 和 Outlet 组织 UI;它仍依赖浏览器 API。原生方案更轻但维护成本高,适合很小的应用或特殊路由需求。

75. React 为什么要自己实现调度器, 而不是直接使用 requestIdleCallback ? ​

答案:

requestIdleCallback 的支持、触发时机和 deadline 在浏览器间不完全一致,忙碌页面可能长期得不到回调,而且它不适合精确表达 React 的优先级、延迟任务和可取消任务。React 的 Scheduler 自己维护最小堆任务队列,按过期时间和优先级选择任务,并结合 MessageChannel、时间片和让出机制调度;这样既能兼容环境,也能保证紧急更新优先。

76. Vue中的 ref、toRef 和 toRefs 有什么区别? ​

答案:

ref(value) 创建独立的 ref 容器,适合基础值或需要整体替换的对象,访问值要用 .value。toRef(obj, key) 为响应式对象的某个属性创建双向关联 ref,修改任一方都会同步;即使属性暂时不存在也能保持连接。toRefs(obj) 批量把对象已有属性转成 ref,常用于解构仍保留响应式,但新增属性不会自动出现在结果中。

77. Vue 中的路由守卫是什么,可以在什么场景下使用? ​

答案:

路由守卫是 Vue Router 在导航不同阶段执行的拦截与处理函数,包括全局 beforeEach、路由独享 beforeEnter、组件内 beforeRouteLeave/Update/Enter,以及解析完成后的 afterEach。常用于登录鉴权、角色权限、未保存表单确认、动态标题和数据预加载。守卫要返回放行、重定向或取消结果;异步校验必须处理失败和重复导航。

78. 说说你对 React 生态的了解 ​

答案:

React 本身偏 UI 层,生态通常由路由(React Router)、请求与缓存(TanStack Query、SWR)、全局状态(Redux Toolkit、Zustand、Jotai)、表单校验、组件库、测试和构建框架组成。Next.js、Remix 等提供 SSR、静态生成和全栈能力。选择时要按数据流、渲染模式和团队维护成本取舍,避免仅为小状态引入重量级全局方案。

79. 说说 React 中,scheduler 的调度机制原理 ​

答案:

Scheduler 把更新包装成带优先级和过期时间的任务,放入按 sortIndex 排序的最小堆:延迟任务按开始时间排序,就绪任务按 expirationTime 排序。通过 MessageChannel 触发工作循环,每轮在 deadline 前执行多个任务;若任务未完成就返回 continuation,必要时让出主线程。过期任务会被提升处理,最终 React 再按 lane 优先级执行协调和提交。

80. React 中的 useRef 是如何实现的? ​

答案:

useRef(initialValue) 在首次渲染时创建 { current: initialValue },随后把这个对象保存在当前 Fiber 的 Hook 链表中;更新渲染只取回同一对象,不会因修改 current 触发重新渲染。可概括为:const hook = mountWorkInProgressHook(); hook.memoizedState = { current: value }; return hook.memoizedState;更新阶段返回已有 memoizedState。它适合保存 DOM 引用或跨渲染可变值。

81. React 中的 forwardsRef,作用是什么, 有哪些使用场景? ​

答案:

forwardRef 让函数组件接收父组件传来的 ref,再把它转发给内部 DOM 或子组件;普通函数组件默认不会把 ref 当作 props。典型场景是封装输入框并让父组件聚焦、测量或调用 useImperativeHandle 暴露的有限方法,也适合组件库透传 ref。它应保持 ref 语义清晰,避免把内部实现细节大面积暴露。React 19 可直接将 ref 作为 prop,旧写法仍兼容。

82. React 19 有哪些新特性? ​

答案:

React 19 的重点包括:函数组件可直接接收 ref;Actions 与 useActionState 简化表单提交、pending 和错误处理;表单支持 action、useFormStatus;新增 useOptimistic;use 可读取 Promise 或 Context;支持文档元数据、资源预加载和更好的 hydration 错误信息。还包含服务端组件与 Server Actions 的生态协作,但具体能力取决于框架和构建配置,不能把实验特性一概当作稳定 API。

83. React 中,如何避免使用 context 时, 引起整个挂载节点树的重新渲染 ​

答案:

Context value 变化会通知使用该 Context 的消费者,不能靠 React.memo 阻止真正读取它的组件更新。优化重点是让 value 引用稳定(useMemo、useCallback),拆分 Context,使变化范围更小,并把 Context 读取隔离在很薄的容器中,再把普通 props 传给 memo 子组件。高频或大范围状态可使用 selector 型状态库;不要为了优化而滥用稳定化,先用 Profiler 定位。

84. React 的循环渲染中,为什么不推荐使用 index 作为元素的 key? ​

答案:

key 用来标识同级列表项的身份。使用 index 时,一旦列表插入、删除或排序,后续元素的 key 会整体变化,React 可能复用错误的 Fiber、DOM 和组件状态,导致输入框内容或动画错位。应使用数据中稳定且唯一的 id;只有列表顺序永远不变、没有增删排序且项无本地状态时,index 才勉强可用。

85. vue 中 Scoped Styles 是如何实现样式隔离的?说说具体的原理 ​

答案:

Vue 的 scoped 会在编译阶段为当前组件生成唯一属性,如 data-v-abc123,并给模板元素加上该属性,同时改写 CSS 选择器为 .title[data-v-abc123],从而限制匹配范围。子组件根节点可能同时带父、子作用域属性,便于布局;使用 :deep() 可穿透到子组件,:slotted() 处理插槽内容,:global() 声明全局样式。它是选择器隔离,不是真正的 Shadow DOM。

86. Vue 有了数据响应式,为何还要 diff ? ​

答案:

响应式解决的是“哪些组件或副作用需要更新”:它追踪数据与渲染函数的依赖并触发更新;diff 解决的是“更新时真实 DOM 如何最小化变更”:比较新旧虚拟 DOM,复用节点并计算属性、文本、子节点的增删改。响应式能缩小更新入口,但一次组件更新仍可能产生新的 VNode 树,因此仍需要 diff;Vue 3 再用编译器标记动态节点进一步跳过静态部分。

87. vue3 为什么不需要时间分片? ​

答案:

“Vue 3 不需要时间分片”并非绝对结论,而是其默认渲染器没有像 React Concurrent 那样把组件协调拆成可中断任务。Vue 3 通过响应式依赖缩小更新范围、编译器静态提升和 patch flag 跳过静态节点,再用异步队列批量更新,通常已足够高效。超大同步计算仍会阻塞主线程,应自行拆分任务、虚拟列表或使用 Web Worker;性能优化不能替代通用调度器。

88. vue3 为什么要引入 Composition API ? ​

答案:

Composition API 把同一业务能力的状态、计算、监听和生命周期代码集中在一起,解决复杂组件中 Options API 逻辑按选项分散的问题;普通函数即可抽成 composable,复用比 mixin 更清晰,也避免命名冲突和来源不明。它还改善了 TypeScript 类型推导、组合能力和逻辑测试。Options API 仍适合简单组件,两者可以在同一项目中按场景选择。

89. 谈谈 Vue 事件机制,并手写$on、$off、$emit、$once ​

答案:

Vue 2 的事件机制本质是实例上的事件总线:按事件名保存回调数组。核心实现如下:

js
function initEvents(vm) { vm._events = Object.create(null) }
function $on(name, fn) { (this._events[name] ||= []).push(fn); return this }
function $off(name, fn) { if (!name) this._events = Object.create(null); else if (!fn) delete this._events[name]; else this._events[name] = (this._events[name] || []).filter(cb => cb !== fn); return this }
function $emit(name, ...args) { for (const fn of this._events[name] || []) fn(...args); return this }
function $once(name, fn) { const once = (...args) => { this.$off(name, once); fn(...args) }; return this.$on(name, once) }

实际实现还需兼容事件数组、错误处理和同名回调;Vue 3 移除了实例事件 API,推荐 emit 或外部事件库。

90. computed 计算值为什么还可以依赖另外一个 computed 计算值? ​

答案:

读取外层 computed 时,Vue 会在其 getter 执行期间把当前活跃的 watcher 设为外层 watcher;访问内层 computed 会先触发内层 getter,再把内层计算属性的依赖通过 depend() 转发给外层。因此依赖链会被完整收集:基础响应式数据变化使内层失效,并继续通知外层。computed 自身有缓存,只在依赖变化后重新计算,且依赖应保持同步、避免副作用。

91. 说一下 vm.$set 原理 ​

答案:

Vue 2 的 $set(target, key, value) 先处理数组索引:通过 splice 触发响应式;若对象已有该属性则直接赋值;若目标不是已观测对象也直接赋值。对已观测对象的新属性,它调用 defineReactive 定义 getter/setter,再通过对象的 Observer.dep.notify() 通知依赖更新。它不能解决 Vue 2 根数据对象和实例 $data 运行时新增属性的限制;Vue 3 通常无需 $set。

92. 怎么在 Vue 中定义全局方法? ​

答案:

Vue 2 可在插件安装时写入 Vue.prototype.$api = api,组件通过 this.$api 使用;Vue 3 应写入 app.config.globalProperties.$api = api,并可用 TypeScript 模块声明补充类型。若只是工具函数,优先普通模块导出;若需要注入配置或可替换实例,优先 provide/inject。全局混入会污染所有组件并增加隐式耦合,通常不如插件或显式依赖。

93. Vue3 中父组件怎么监听到子组件的生命周期? ​

答案:

最清晰的方式是子组件在 onMounted、onUnmounted 等钩子中 emit('mounted')、emit('unmounted'),父组件监听自定义事件。Vue 3 还支持父组件写 @vue:mounted、@vue:updated、@vue:unmounted 监听组件 VNode 生命周期,适合无需修改子组件的场景,但偏底层。ref 加 defineExpose 适合父组件调用子组件公开方法,不应直接读取内部实例字段。

94. vue组件里写的原生addEventListeners监听事件,要手动去销毁吗?为什么? ​

答案:

通常要手动移除。addEventListener 注册的是浏览器级监听,Vue 卸载组件不会自动知道并清理它;若不移除,可能保留闭包引用、重复绑定并在组件销毁后继续执行。必须使用同一个函数对象和相同的捕获选项调用 removeEventListener,在 Vue 3 的 onMounted 中添加、onUnmounted 中移除;若用 AbortController,也可在卸载时 abort()。

95. vue中,推荐在哪个生命周期发起请求? ​

答案:

没有唯一答案:若请求只为准备组件数据,Vue 2 可在 created,Vue 3 可在 setup 或 onMounted 发起;这样不必等待 DOM。只有请求依赖 DOM 或浏览器 API 时才放 mounted/onMounted。实际还要处理 loading、错误、组件卸载后的竞态和取消请求;SSR 场景不能依赖 mounted,应使用可在服务端执行的数据预取机制。

96. Vue中,created和mounted两个钩子之间调用时间差值受什么影响? ​

答案:

差值主要由 created 之后到挂载完成之间的同步工作决定,包括 beforeMount、模板或渲染函数执行、组件树创建、虚拟 DOM patch、真实 DOM 插入,以及这些阶段钩子中的同步代码。组件数量、模板复杂度、指令和设备性能都会影响它。created 中发起的异步请求通常不会阻塞 mounted,除非其同步部分或返回后的更新参与了挂载流程。

97. 下面代码中,点击 “+3” 按钮后,age 的值是什么? ​

js
import { useState } from 'react';

export default function Counter() {
  const [age, setAge] = useState(42);
  function increment() {
    setAge(age + 1); 
  }
  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
    </>
  );
}

答案:

点击后 age 是 43。同一事件处理函数中三次调用都读取本次渲染的旧值 42,三次都提交 setAge(43),React 会批量处理更新。若要累加三次,应写成 setAge(a => a + 1),函数式更新会按队列依次得到 43、44、45。

98. 子组件是一个 Portal,发生点击事件能冒泡到父组件吗? ​

答案:

能。Portal 虽把节点挂到另一处 DOM 容器,但它仍属于原来的 React 组件树;React 合成事件沿 React 树传播,所以会冒泡到父组件。注意这不是 DOM 原生父子关系,原生事件的冒泡路径仍按真实 DOM 树决定,跨 root 等场景还需单独处理。

99. React 为什么要废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个生命周期钩子?它们有哪些问题呢?React 又是如何解决的呢? ​

答案:

这三个生命周期发生在 render 阶段。Fiber 允许 render 工作被打断、重试或丢弃,因此其中的网络请求、订阅等副作用可能执行多次,行为不可靠。React 16.3 起标记为 UNSAFE_,推荐把副作用移到 componentDidMount/componentDidUpdate 或函数组件的 Effect;派生状态可用 getDerivedStateFromProps,它是静态且应保持无副作用。

100. 为什么 react 需要 fiber 架构,而 Vue 却不需要? ​

答案:

Fiber 不是 React 必需的渲染算法,而是为可中断、可恢复的协调和优先级调度设计的数据结构。React 的组件计算常需重新执行,复杂树上同步更新会阻塞输入,因此需要拆分工作。Vue 主要依靠编译优化、响应式依赖追踪和异步批量更新减少无效工作,传统场景不需要同样的调度模型;这不是“Vue 没有性能优化”,而是架构取舍不同。

101. React 中为什么不直接使用 requestIdleCallback? ​

答案:

requestIdleCallback 的空闲时机和时间预算受浏览器控制,后台标签页或持续繁忙时可能长期不执行,也难表达 React 的多级优先级和可中断任务。React 因而使用自己的 Scheduler,根据任务过期时间和优先级调度,并通过 MessageChannel 等机制让出主线程,从而获得跨环境更一致的更新行为。

102. react 和 react-dom 是什么关系? ​

答案:

react 是与渲染平台无关的核心包,提供元素、组件、Hooks、Context 等能力;react-dom 是 Web DOM 渲染器,负责把 React 元素协调并提交到浏览器 DOM,还提供 createRoot、Portal 等 DOM API。分离后,同一 React 核心可以配合 React Native 等其他渲染器使用。

103. React Portals 有什么用? ​

答案:

Portal 用于把子树渲染到当前 DOM 层级之外,例如 createPortal(children, container)。典型场景是弹窗、下拉菜单、提示层和全屏遮罩,可避免父级 overflow、定位上下文或 z-index 的限制。它只改变 DOM 挂载位置,不改变 React 树中的 props、Context 和合成事件归属。

104. 说说你对渐进式框架的理解 ​

答案:

渐进式框架指能力可以按项目复杂度逐步采用,而不是一次性接受完整技术栈。Vue 可先用于模板、组件和表单,再按需要引入路由、状态管理、构建工具和 SSR。它降低学习与迁移成本,也保留工程化扩展空间;但渐进不等于随意混用,项目仍应统一约定和边界。

105. vue的祖孙组件的通信方案有哪些? ​

答案:

优先用 provide/inject 做跨层级依赖传递,祖组件提供、孙组件注入;需要双向交互时可提供响应式对象和操作函数。简单场景也可逐层 props/emit,但层级深会产生透传。跨多个业务区域或共享状态复杂时使用 Pinia/Vuex;事件总线会隐藏依赖,通常只适合非常局部的事件通知。

106. 如何打破 scope 对样式隔离的限制? ​

答案:

Vue 3 推荐使用 :deep(.child)(旧版本常见 ::v-deep、/deep/)穿透 scoped 选择器,例如 .wrap :deep(.button) { ... }。也可把规则放到不加 scoped 的全局样式中,或通过组件提供的 class/变量定制。穿透会扩大影响范围,应尽量加稳定的组件根类名,避免覆盖内部实现细节。

107. Scoped Styles 为什么可以实现样式隔离? ​

答案:

<style scoped> 编译时会为当前组件生成唯一属性,如 data-v-xxxx,并把模板节点和 CSS 选择器都改写为带该属性的形式:.title 变为 .title[data-v-xxxx]。因此规则只匹配本组件节点。子组件根节点通常会同时带父子作用域属性,便于布局;但子组件内部节点不会自动匹配父组件的 scoped 规则,需用 :deep。

108. vue 中怎么实现样式隔离? ​

答案:

Vue 中常用三种方式:组件级 <style scoped> 由编译器添加唯一属性实现隔离;CSS Modules 生成局部类名,通过 $style 使用;全局样式则不加 scoped,适合重置、主题和公共工具类。组件库定制时可结合 CSS 变量或 :deep,并控制选择器范围,避免全局覆盖造成副作用。

109. created 和 mounted 有什么区别? ​

答案:

created 发生在实例创建完成、数据观测和计算属性等初始化之后,此时可访问 data、props、computed,但 DOM 尚未生成,不能可靠操作元素。mounted 发生在组件 DOM 首次挂载后,适合读取尺寸、初始化依赖 DOM 的第三方库。两者都不保证子组件全部挂载,跨组件 DOM 应配合 nextTick。

110. Vue是怎么把template模版编译成render函数的? ​

答案:

以 Vue 2 为例,模板先经解析器生成 AST,包含元素、属性、指令和文本插值;优化阶段标记静态节点;代码生成器再把 AST 转成 with(this){return _c(...)} 形式的 render 函数代码,最后通过 new Function 编译执行。Vue 3 则生成更优化的 render 函数和 patch flag,以减少运行时比较。

111. v-model 的原理是什么样的? ​

答案:

在原生表单元素上,v-model 是语法糖:文本输入约等于 :value="x" @input="x=$event.target.value",复选框、单选框会按类型使用 checked 和 change。组件上默认展开为 modelValue 与 update:modelValue(Vue 2 常见为 value 与 input)。它还负责修饰符如 .number、.trim、.lazy。

112. 说说 Vuex 的原理 ​

答案:

Vuex 以单一 store 保存共享状态,组件通过 commit 调用同步 mutation 修改 state,通过 dispatch 调用 action 处理异步逻辑,getter 负责派生读取。store 的响应式变化触发订阅组件更新;模块可拆分命名空间。核心约束是修改路径可追踪、状态集中且更新可预测,Vue 3 项目通常优先考虑 Pinia。

113. vue 的响应式开发比命令式有哪些优势? ​

答案:

命令式开发要逐步描述“何时、怎样”改 DOM,状态和视图容易分散,更新逻辑也容易遗漏。响应式开发只声明状态与视图的关系,依赖变化后框架自动调度更新,代码更接近业务意图,组件复用和维护成本较低。边界是需要理解依赖收集、异步批量更新和不可变/可变数据的约束,复杂副作用仍要显式管理。

114. useMemo 和 useCallback 有什么区别? ​

答案:

useMemo 缓存计算结果,返回一个值;useCallback 缓存函数引用,本质上是 useMemo(() => fn, deps)。它们只在依赖未变时复用,常用于昂贵计算或把稳定回调传给 memo 子组件。缓存本身也有比较和内存成本,普通计算、未被依赖的回调不应盲目使用。

115. 如果在 useEffect 的第一个参数中 return 了一个函数,那么第二个参数分别传空数组和传依赖数组,该函数分别是在什么时候执行? ​

答案:

Effect 返回的函数是清理函数。依赖传 [] 时,组件挂载后执行一次 Effect,卸载时执行清理;依赖数组 [a,b] 时,挂载后执行,之后在依赖变化导致的新 Effect 执行前先清理旧 Effect,组件卸载时再清理最后一次。开发模式 Strict Mode 可能额外执行一次挂载清理检查。

116. useEffect 的第二个参数, 传空数组和传依赖数组有什么区别? ​

答案:

传 [] 表示 Effect 只对应初次挂载,之后不因 props 或 state 变化重新执行(卸载仍会清理);传 [a,b] 表示首次执行,且仅当 a 或 b 按 Object.is 判断发生变化时重新执行。依赖应包含 Effect 使用的响应式值,否则可能读取闭包中的旧值;空数组不是绕过依赖规则的手段。

117. 怎么判断一个对象是否是 React 元素? ​

答案:

React 元素是 createElement 或 JSX 生成的不可变描述对象。推荐使用 React.isValidElement(value) 判断,它能识别当前 React 版本的合法元素,并排除普通对象、组件类型和数组。不要只检查 type 或手写 $$typeof,后者依赖内部实现;注意字符串、数字、Fragment 数组等可渲染节点不一定都是单个 React element。

118. React 和 Vue 在技术层面有哪些区别? ​

答案:

React 更偏 JavaScript 组件模型,JSX 中显式描述 UI,状态更新通常遵循不可变数据;Vue 以模板、指令和编译优化为主,响应式系统可直接追踪对象属性变化。React 的协调与生态更开放,Vue 的官方路由、状态和工程方案更一体化。两者都采用组件化、虚拟 DOM 和单向数据流,具体性能取决于代码与场景。

119. React 中,怎么实现父组件调用子组件中的方法? ​

答案:

函数组件中用 forwardRef 暴露 useImperativeHandle 定义的少量命令式方法;类组件可直接通过 ref 得到实例。示例:const Child=forwardRef((_,ref)=>{useImperativeHandle(ref,()=>({focus:()=>input.current.focus()})); return <input ref={input}/>}),父组件用 const ref=useRef(); <Child ref={ref}/> 后调用 ref.current.focus()。应优先用 props/回调,ref 只处理聚焦、滚动等命令式需求。

120. taro 2.x 和 taro 3 最大区别是什么? ​

答案:

Taro 2.x 主要把 React/Vue 代码编译为各端平台模板和逻辑,运行时以平台适配为主;Taro 3 引入跨端运行时和自定义渲染器,组件树可在运行时以类似 React 的方式渲染到微信、H5、RN 等端。3.x 因而更接近 Web 开发模型、扩展性更强,但运行时体积和兼容性成本也更高。

121. taro 的实现原理是怎么样的? ​

答案:

Taro 通过编译器与运行时适配实现跨端:编译阶段解析 JSX/TS、样式和配置,按目标端转换语法并打包;运行时维护组件状态、生命周期和事件,再通过平台适配层把虚拟节点映射为微信小程序、H5 或其他端的视图更新。不同平台的组件、样式和 API 仍有差异,不能把跨端当作完全无条件兼容。

122. 单页应用如何提高加载速度? ​

答案:

先用性能面板和打包分析定位瓶颈,再按收益优化:路由级代码分割与懒加载,压缩并按需加载 JS/CSS,优化图片格式和尺寸并懒加载;利用 HTTP 缓存、CDN、预加载关键资源;减少首屏组件与第三方脚本,避免长任务,并通过 SSR/SSG 或预渲染改善首屏 HTML。优化后用 LCP、INP、CLS 和真实用户数据验证。

123. 实现 useUpdate 方法,调用时强制组件重新渲染 ​

答案:

js
import { useReducer } from 'react';

export function useUpdate() {
  const [, update] = useReducer(v => v + 1, 0);
  return update;
}

调用返回的函数即可触发一次 state 更新并重新渲染,例如 const forceUpdate = useUpdate(); forceUpdate()。时间和额外空间复杂度均为 O(1)。它不应替代正确的状态建模;React 仍可能批量更新,且组件卸载后调用属于错误用法。

124. 怎么在代码中判断一个 React 组件是 class component 还是 function component? ​

答案:

可用 component.prototype?.isReactComponent 识别传统类组件:typeof component === 'function' && component.prototype?.isReactComponent 为真时通常是 class component,否则是 function component。该判断针对 React 类组件约定,不能可靠区分所有高阶组件或被包装的组件;生产代码更应在定义时保留类型信息,或通过 memo、forwardRef 等包装结构分别处理。

125. 说说对 React 中Element、Component、Node、Instance 四个概念的理解 ​

答案:

Element 是描述 UI 的不可变普通对象,例如 JSX 的结果;Component 是生成 Element 的函数或类,接收 props 并参与协调;Node 是更宽泛的可渲染节点,包含 element、文本、数组、Portal 等;Instance 是运行中的组件或宿主对象,类组件有实例,函数组件没有可供开发者访问的组件实例。区分这些概念有助于理解“描述”与“实际运行对象”。

126. react 中怎么捕获异常? ​

答案:

渲染阶段、生命周期和构造函数中的错误可由错误边界捕获:类组件实现 static getDerivedStateFromError 显示降级 UI,并用 componentDidCatch 上报日志。错误边界不能捕获事件处理器、异步回调、服务端渲染或自身错误;事件代码用 try/catch,Promise 用 catch,全局同步错误和未处理拒绝可分别监听 error 与 unhandledrejection。

127. 使用 redux 有哪些原则? ​

答案:

Redux 的核心原则是:全局状态有单一 store;state 只读,必须通过描述事件的 action 更新;reducer 是纯函数,根据旧 state 和 action 返回新 state,不能直接修改旧对象、发起异步或产生副作用。副作用应放在 middleware、thunk 或 listener 中。不可变更新便于引用比较、预测行为、测试和时间旅行调试。

128. 使用 React hooks 怎么实现类里面的所有生命周期? ​

答案:

Hooks 不是逐一复刻生命周期,而是按同步外部系统来组织逻辑:函数体对应 render,useState 初始化状态,useEffect(fn, []) 近似挂载,带依赖的 Effect 覆盖更新,返回函数负责清理/卸载;useLayoutEffect 适合 DOM 布局前同步处理,React.memo 可做 props 浅比较。错误边界仍需类组件或现成库,不能用 Hook 直接实现 componentDidCatch。

129. React.memo() 和 useMemo() 的用法是什么,有哪些区别? ​

答案:

React.memo 包装函数组件,父组件重新渲染时会对 props 做浅比较,引用未变则跳过该子组件渲染;可传自定义比较函数。useMemo 是 Hook,缓存组件内部某段计算的返回值,依赖变化才重算。前者优化组件渲染,后者优化计算结果,二者都不是语义保证;缓存本身也有比较和内存成本,应以性能分析为依据。

130. 实现一个 useTimeout Hook ​

答案:

js
import { useEffect, useRef } from 'react';

export function useTimeout(callback, delay) {
  const callbackRef = useRef(callback);
  useEffect(() => { callbackRef.current = callback; }, [callback]);
  useEffect(() => {
    if (delay == null) return;
    const id = setTimeout(() => callbackRef.current(), delay);
    return () => clearTimeout(id);
  }, [delay]);
}

回调用 ref 保持最新,避免定时器捕获旧闭包;delay 为 null 时暂停,卸载或延迟变化时清理定时器。复杂度 O(1),并且不会因回调引用变化反复创建定时器。

131. React18新特性 ​

答案:

React 18 的核心变化是并发渲染能力:使用 createRoot 后启用新的更新调度;Promise、定时器等异步场景也支持自动批处理。startTransition/useTransition 可标记低优先级更新,useDeferredValue 可延后派生值;新增 useId、useSyncExternalStore、useInsertionEffect,并增强 Suspense 与流式 SSR。升级时注意 StrictMode 开发环境可能重复执行副作用,React Server Components 则主要由框架提供,并非所有 React 18 应用自动拥有。

132. 说说你对 React Hook的闭包陷阱的理解,有哪些解决方案? ​

答案:

闭包陷阱指异步回调捕获了创建它那次渲染的 state/props,因此定时器、订阅或请求回调可能一直读旧值。常见处理方式:状态更新依赖旧值时使用函数式更新 setCount(v => v + 1);让 effect 依赖完整并在清理函数中取消订阅或请求;只需读取最新可变值时用 useRef;回调要传给子组件时用 useCallback,但它只能稳定引用,不能自动修复闭包。

133. 说说你对 useReducer 的理解 ​

答案:

useReducer 用 reducer (state, action) => nextState 集中描述状态转移,再通过 dispatch(action) 触发更新,适合状态字段多、更新规则复杂或多个操作相互关联的组件。reducer 应保持纯函数,不直接修改原 state;初始化昂贵时可传第三个参数做惰性初始化。它不是异步状态库,副作用应放在 effect 或事件处理器中,dispatch 后读取 state 仍要遵循 React 的批处理与闭包规则。

134. 讲讲 React.memo 和 JS 的 memorize 函数的区别 ​

答案:

React.memo 缓存的是组件渲染结果的复用条件:props 浅比较相等时跳过该组件本次渲染,但组件自己的 state 或 context 变化仍会更新。JavaScript 的 memoize 是通用函数缓存,把参数映射到返回值,常需自己设计 key、缓存生命周期和引用相等规则。两者都可能因输入对象、函数每次新建而失效,也都要考虑缓存占用;React.memo 不能替代业务函数的结果缓存。

135. 我们应该在什么场景下使用 useMemo 和 useCallback ? ​

答案:

useMemo 用于缓存一次计算的结果,适合计算确实昂贵且依赖在多次渲染间稳定的场景,或需要稳定对象引用来配合 memo。useCallback(fn, deps) 等价于缓存函数引用,适合把回调传给经过 memo 的子组件或作为 effect 依赖。依赖必须完整,不能把它们当作正确性保证;简单计算、普通内联函数或依赖本就频繁变化时,缓存开销可能更大。

136. 说说你对 dangerouslySetInnerHTML 的理解 ​

答案:

dangerouslySetInnerHTML={{__html: html}} 会绕过 React 的文本转义,把字符串作为 HTML 插入 DOM,适合可信的富文本或服务端已生成的受控内容。它会覆盖该节点的 children,不能同时设置子节点;更新时应保证 html 与 UI 状态一致。最大风险是 XSS,任何用户或外部来源内容都必须先经过可靠的 HTML 白名单清洗,不能只依赖字符串替换;也要注意脚本、URL 属性和 SSR 注入风险。

137. react是否支持给标签设置自定义的属性,比如给video标签设置webkit-playsinline? ​

答案:

React 支持自定义 DOM 属性,但要区分标准属性、数据属性和非标准属性。data-*、aria-* 可直接使用;浏览器厂商属性通常按 React DOM 属性规则写,例如 webkit-playsinline 应写成 playsInline,React 会输出对应属性。未知属性也可能被透传,但不要依赖大小写或随意拼写。若第三方属性必须手动设置,可在 ref 中调用 element.setAttribute,并注意 SSR 与客户端输出一致。

138. 你常用的 React Hooks 有哪些? ​

答案:

常用 Hooks 包括:useState 管理局部状态,useReducer 管理复杂状态转移,useEffect 处理与外部系统同步,useLayoutEffect 在浏览器绘制前读取或修正布局,useMemo/useCallback 缓存值和函数,useRef 保存不触发渲染的可变值或 DOM 引用,useContext 读取上下文。还可编写自定义 Hook 复用状态逻辑。Hooks 必须在组件或自定义 Hook 顶层调用,不能放进条件、循环或普通函数。

139. 如何让 useEffect 支持 async/await? ​

答案:

不要把 effect 回调直接声明为 async,因为 async 函数返回 Promise,而 React effect 的返回值只应是清理函数。应在 effect 内定义异步函数并调用:

js
useEffect(() => {
  let cancelled = false;
  (async () => {
    const data = await fetch('/api').then(r => r.json());
    if (!cancelled) setData(data);
  })();
  return () => { cancelled = true; };
}, []);

生产代码还应配合 AbortController 取消请求,避免卸载后竞态写入。

140. 说说你对自定义hook的理解 ​

答案:

自定义 Hook 是以 use 开头、内部可调用其他 Hooks 的函数,用来抽取可复用的状态逻辑和副作用,例如分页、订阅、媒体查询。它复用的是逻辑而不是同一份状态:每次调用都有独立的 state/ref;只有通过 context、外部 store 或参数回调才能共享数据。Hook 应遵守顶层调用规则,明确依赖、清理和返回值契约,避免把组件 JSX 或不必要的全局副作用藏进去。

141. 说说你对 useMemo 的理解 ​

答案:

useMemo(factory, deps) 在函数组件中缓存 factory 的返回值,首次渲染执行,后续仅当依赖以 Object.is 判断发生变化时重新计算。它适合昂贵计算、派生数据或稳定对象引用,不适合用来保证业务逻辑正确性,因为 React 可能丢弃缓存。依赖数组必须列出计算中读取的响应式值;空数组只适用于确实不依赖渲染值的初始化计算,并且要考虑开发 StrictMode 的额外调用。

142. 说说你对 useContext 的理解 ​

答案:

useContext(Context) 读取最近的 Context.Provider 值,Provider 的 value 引用变化会让所有读取该 context 的组件重新渲染,即使组件被 memo 包装也一样。适合主题、语言、登录用户等跨层级且变化频率不高的数据。应把 context 拆小、稳定 value 中的对象和函数,或使用 selector/store 降低无关更新;没有 Provider 时读取 createContext 的默认值。Context 不是自动持久化或全局状态管理的替代品。

143. 为什么不能在循环、条件或嵌套函数中调用 Hooks? ​

答案:

Hooks 的状态由 React 按调用顺序关联到当前组件实例;如果把 Hook 放进条件、循环或嵌套函数,某次渲染的调用次数或顺序变化,后续 state 就会错位,依赖 effect 也无法稳定匹配。因此必须在组件或自定义 Hook 的顶层无条件调用。把条件放进 Hook 内部,或在组件外选择不同组件;使用 eslint-plugin-react-hooks 的 rules-of-hooks 规则可提前发现这类错误。

144. 说说 Vue 中 CSS scoped 的原理 ​

答案:

Vue 2 的 scoped 并非真正的 Shadow DOM,而是编译期给当前组件元素加唯一属性,如 data-v-a1b2c3,同时把选择器改写为 .item[data-v-a1b2c3],从而限制样式作用范围。子组件根节点可能同时带父子 scope 属性,因此父级样式可命中子组件根节点,但通常不能直接命中其内部元素;需要 ::v-deep(旧写法 /deep/ 或 >>>)。它主要解决命名隔离,不能替代 CSS 模块或运行时封装。

145. Vue 模板是如何编译的 ​

答案:

以 Vue 2 为例,模板编译先解析 HTML 字符串生成 AST,完成指令、插值和静态节点分析,再优化静态树,最后把 AST 生成 render 函数和静态渲染函数。运行时执行 render 得到 VNode,随后由 patch 算法创建或更新真实 DOM。Vue 3 则使用更快的编译器,并通过静态提升、patch flag、事件缓存等编译提示减少运行时比较;模板不能直接被浏览器执行。

146. vue3中怎么设置全局变量? ​

答案:

Vue 3 设置全局能力通常有三种方式:应用级配置用 app.config.globalProperties.$api = api,组件中通过 this.$api 访问;更推荐用 provide/inject 注入服务,便于类型约束和测试;真正的共享响应式状态可用 Pinia。插件应在 app.use(plugin) 中完成注册。不要把大量可变业务状态都塞进 globalProperties,因为它缺乏依赖关系的明确表达,也不利于 SSR 隔离。

147. 刷新浏览器后,Vuex的数据是否存在?如何解决? ​

答案:

刷新浏览器会销毁当前页面的 Vue 实例,Vuex store 通常只存在内存中,因此状态会恢复初始值。解决方案是把需要保留的状态持久化到 localStorage、sessionStorage、IndexedDB 或服务端;应用启动时读取并提交到 store,变化时再同步保存。敏感 token 不应无条件放 localStorage,应结合 HttpOnly Cookie 等方案;持久化还要处理版本迁移、过期、序列化失败和多标签页同步。

148. 说下Vite的原理 ​

答案:

Vite 开发时利用浏览器原生 ESM,按需编译被请求的模块,源码变化后通过 WebSocket HMR 精确更新,避免传统 bundler 每次重打整包;依赖则用 esbuild 预构建并缓存,以兼容 CommonJS 和减少请求。生产构建默认交给 Rollup,完成依赖解析、分包、Tree Shaking 和资源优化。它的速度主要来自开发期按需服务,不代表生产完全不打包,也不能省略浏览器不支持的语法转换。

149. vue 中 $route 和 $router 有什么区别? ​

答案:

在 Vue Router 中,$router 是路由器实例,负责导航和控制历史记录,如 push、replace、go;$route 是当前路由状态对象,包含 path、params、query、meta 等信息,用于读取当前地址。导航后 $route 会更新,而 $router 通常是同一个实例。Composition API 中对应 useRouter() 和 useRoute();不要直接修改 $route,应通过 router 导航。

150. 说说 vue3 中的响应式设计原理 ​

答案:

Vue 3 响应式基于 Proxy:读取响应式对象属性时,当前 active effect 被记录到对应依赖集合;写入、删除或数组变化时触发相关 effect,组件据此重新渲染。reactive 适合对象并返回代理,ref 通过 .value 包装任意值,模板会自动解包;computed 对依赖结果懒执行并缓存。Proxy 能拦截新增属性、删除和数组索引,仍需注意解构丢失响应式、替换 reactive 根对象以及深层代理成本。

151. vue路由中,history和hash两种模式有什么区别? ​

答案:

hash 模式把路由放在 # 后,变化通常只触发 hashchange,不会向服务器请求新路径,兼容性好,但 URL 带井号、SEO 和美观性较弱。history 模式使用 History API 的 pushState/replaceState,URL 更自然,可配合服务端正确回退到 index.html;否则刷新深层路径会 404。两者都需要处理后退、前进和部署基路径,选择取决于服务器配置与 SEO 需求。

152. react中,父子组件的生命周期执行顺序是怎么样的? ​

答案:

以首次挂载为例,React 的 render 阶段通常按父到子递归执行函数组件;提交阶段 DOM 更新后,类组件 componentDidMount 和子孙的挂载副作用按子树完成,实际观察还会受 effect 类型、版本和 StrictMode 影响。更新时父组件先重新 render,子组件再 render;提交阶段先处理 DOM,再执行 layout effect,浏览器绘制后执行 passive effect。卸载则是子组件先于父组件清理。不要依赖细节顺序承载业务逻辑。

153. 说说React事件和原生事件的执行顺序 ​

答案:

React 事件委托到根容器(旧版本通常是 document),先触发 React 的合成事件处理器,再继续原生事件冒泡到更外层;同一节点上原生监听器与 React 监听器的先后还取决于注册位置和 React 版本,不能只凭“React 一定先/后”判断。事件传播仍有捕获、目标、冒泡阶段,stopPropagation 只阻止继续传播,不等于取消默认行为;需要 preventDefault。跨根节点、Portal 和原生监听混用时应实测并统一事件边界。

154. VNode 有哪些属性? ​

答案:

Vue 2 常见 VNode 属性包括:tag 标签名、data 节点数据、children 子节点、text 文本、elm 对应真实 DOM、ns 命名空间、context 创建它的组件实例、key diff 标识、componentOptions 组件选项、componentInstance 组件实例,以及 isComment、isStatic、isCloned 等标志。不同 Vue 版本内部字段会变化,业务代码不应依赖未公开字段;VNode 本质是描述 UI 的轻量对象,不是真实 DOM。

155. Vue2.0为什么不能检查数组的变化,该怎么解决? ​

答案:

Vue 2 通过 Object.defineProperty 劫持已存在的对象属性,数组索引和 length 的变化无法被可靠拦截;同时直接给对象新增属性也不会自动建立响应式依赖。Vue 对数组原型的 push、pop、shift、unshift、splice、sort、reverse 做了包装,因此这些变异方法可触发更新。推荐使用 Vue.set(obj, key, value)、this.$set 或 splice;Vue 3 改用 Proxy,从机制上覆盖新增属性、删除和索引等操作。

156. 说说Vue 页面渲染流程 ​

答案:

Vue 页面首次渲染大致经历:创建实例并初始化响应式数据、props、computed、watch 和生命周期;编译模板或使用已有 render 函数,执行 render 生成 VNode;patch 将 VNode 转成真实 DOM,挂载到容器并触发 mounted。数据变化时,依赖通知渲染 watcher,在异步队列中合并更新,再次 render 生成新 VNode,通过 diff/patch 最小化修改 DOM,随后执行 updated。卸载时清理 watcher、事件和组件生命周期。

157. react 和 vue 有什么区别? ​

答案:

React 和 Vue 都采用组件化、声明式 UI、虚拟 DOM 与响应式更新,但取舍不同。React 更偏 JavaScript 表达,状态和副作用主要靠 Hooks,生态组合自由;Vue 提供模板、指令、单文件组件和更内置的响应式语法,学习路径更统一。React 更新常由 state/props 触发并依赖不可变数据习惯,Vue 由响应式依赖精确触发。性能不能只按框架下结论,最终取决于组件设计、数据规模、编译优化和使用方式。

158. computed怎么实现的缓存 ​

答案:

computed 的缓存建立在依赖追踪上。首次读取时执行 getter,响应式系统记录它访问的依赖,并缓存返回值;再次读取时若依赖未变化直接返回缓存。依赖触发 setter 后,computed 被标记为 dirty,但通常等到下次读取才重新计算,这就是懒执行。Vue 2 通过 Watcher 的 dirty/evaluate/depend 实现,Vue 3 使用 computed ref 的 dirty 标记和依赖集合。getter 应保持无副作用,否则缓存会带来难以预测的行为。

159. ​vue中computed和watch区别 ​

答案:

computed 用于根据一个或多个响应式值派生结果,默认带缓存,依赖不变就复用,适合模板展示和纯计算;也可通过 getter/setter 实现可写计算属性。watch 监听指定数据变化,每次变化执行回调,默认不缓存,适合请求、日志、节流等副作用,并支持 immediate、deep、flush 等选项。能用纯派生值表达时优先 computed;需要异步或副作用时用 watch,避免用 watch 维护本可计算出的状态。

160. vue-loader做了哪些事情? ​

答案:

vue-loader 让 webpack 能处理 .vue 单文件组件:解析 template、script、style 块,将模板编译为 render 函数,把脚本配置与 render 合并为组件,并为每个样式块调用相应 CSS/预处理器 loader。scoped 样式会生成唯一 scope id,改写选择器和元素属性;还支持 CSS Modules、资源引用、热更新等。Vue 2 配置通常需要 VueLoaderPlugin;Vue 3 则使用匹配版本的 loader 和编译器。它负责编译转换,不是运行时渲染器。

161. Vue 中,假设 data 中有一个数组对象,修改数组元素时,是否会触发视图更新? ​

答案:

Vue 2 中,直接用索引赋值(如 items[index] = value)或直接改 length 通常不会触发响应式更新,应使用 this.$set(items, index, value) 或 splice。push、pop、shift、unshift、sort、reverse 等变异方法已被 Vue 2 劫持。Vue 3 基于 Proxy 能监听索引赋值,但仍建议通过清晰的响应式写法修改;DOM 更新还会批量异步执行,需等待 nextTick。

162. vuex中的辅助函数怎么使用? ​

答案:

Vuex 辅助函数是把 store 映射为组件计算属性或方法的语法糖。mapState、mapGetters 放在 computed 中,分别映射 state 和 getter;mapMutations、mapActions 放在 methods 中,分别映射提交 mutation 和派发 action。可传数组使用原名,也可传对象改别名;模块化 store 还要配合 createNamespacedHelpers 或命名空间参数。

163. Vuex有几种属性,它们存在的意义分别是什么? ​

答案:

Vuex 的核心属性有四类:state 保存全局响应式状态;getters 对 state 做派生计算并可缓存;mutations 以同步方式、可追踪地修改 state;actions 处理异步或复杂业务,再通过 commit 调用 mutation。组件通常通过 dispatch 调 action、通过 commit 提交 mutation;严格模式下不能直接改 state。

164. Vuex 是什么? ​

答案:

Vuex 是 Vue 官方生态中的集中式状态管理方案,适合多个组件共享且需要可追踪修改的数据。store 以响应式 state 为源,组件通过 getter 读取派生值,通过 mutation 以同步提交记录状态变更,通过 action 编排异步逻辑。它能统一数据流和调试时间线,但局部 UI 状态不必放进 Vuex,过度集中会增加复杂度。

165. 下面函数组件的输出分别是什么? ​

下面是一个简单的函数组件,有两个按钮:“alert”、“add”。

如果先点击“alert”按钮,再点击一次“add”按钮,那么弹窗框中的值和页面中展示value分别是什么?

js
const FunctionComponent = () => {
  const [value, setValue] = useState(1)

  const log = () => {
    setTimeout(() => {
      alert(value)
    }, 3000);
  }

  return (
    <div>
      <p>FunctionComponent</p>
      <div>value: {value}</div>
      <button onClick={log}>alert</button>
      <button onClick={() => setValue(value + 1)}>add</button>
    </div>
  )
}

答案:

弹窗会显示 1,页面最终显示 value: 2。点击 alert 时,当前渲染创建的 log 闭包捕获了 value = 1,定时器执行时仍使用这个快照;随后点击 add 触发重新渲染,函数组件中的新 value 变为 2,但不会改变已经创建的旧回调。若想读取最新值,可用 useRef 保存值,或在 effect 中依赖值处理。

166. 谈谈你对Vue中keep-alive的理解 ​

答案:

keep-alive 用于缓存动态组件实例。它会把被切换出去的组件放入缓存,保留其 state 和 DOM 相关状态,重新激活时不必重新创建;配合 include、exclude、max 可按组件名筛选并限制数量。被缓存组件切换时触发 deactivated,再次显示触发 activated,而不是完整的卸载和挂载。它适合列表详情、表单和多标签页,但会占用内存,需合理清理缓存。

167. React 中怎么实现状态自动保存(KeepAlive)? ​

答案:

React 没有内置名为 KeepAlive 的通用组件。通常把状态提升到父组件或 Redux、Context 等外部 store,使组件卸载后数据仍保留;若还要保留 DOM 和局部状态,可使用带缓存能力的第三方 KeepAlive 组件,或通过条件渲染隐藏而不卸载。实现时要处理缓存 key、内存上限、路由切换及副作用清理,不能只依赖 display:none 解决所有场景。

168. useEffect 与 useLayoutEffect 有什么区别? ​

答案:

useEffect 通常在浏览器绘制后运行,不阻塞视觉更新,适合网络请求、订阅和与外部系统同步;useLayoutEffect 在 DOM 提交后、绘制前同步运行,适合测量布局并立即修正位置,避免闪烁。布局 effect 会阻塞绘制,应尽量少用。两者都应返回清理函数,清理会在依赖变化后的下一次 effect 前以及卸载时执行。服务端渲染期间 effect 不运行,依赖浏览器 API 的逻辑要考虑 hydration。

169. React Hooks 在使用上有哪些限制? ​

答案:

Hooks 只能在函数组件或自定义 Hook 的顶层调用,不能放进循环、条件、嵌套函数或事件回调中,且不能在普通 JavaScript 函数里直接调用。原因是 React 依靠每次渲染时 Hook 的固定调用顺序匹配状态。自定义 Hook 名称应以 use 开头;依赖数组要写完整,避免闭包拿到旧值;不要把 Hook 调用放到 try/catch 等会改变执行路径的代码中。

170. 为什么 useState 返回的是数组而不是对象? ​

答案:

useState 返回数组是为了支持位置解构,并允许调用者自由命名状态值和 setter,例如 [count, setCount];如果返回对象,属性名就会被 API 固定,多个 state 还容易产生命名和结构负担。数组也便于一个组件多次调用同一 Hook。它并不表示 state 本身是数组,返回的是二元结构:当前值和更新函数。

171. Redux中的connect有什么作用? ​

答案:

connect 是 React-Redux 的高阶组件,把 Redux store 与 React 组件连接起来。mapStateToProps 将 state 映射为 props,mapDispatchToProps 将 dispatch 或 action creator 映射为 props;store 更新后它会比较映射结果,必要时重新渲染,并订阅和取消订阅 store。现代 React-Redux 更推荐 useSelector 与 useDispatch,但 connect 仍适合类组件和需要优化订阅的场景。

172. Redux 和 Vuex 有什么区别,它们有什么共同思想吗? ​

答案:

Redux 和 Vuex 都强调单向数据流、集中管理共享状态、通过明确的更新入口修改状态,并支持开发工具追踪变更。Redux 通常以 reducer 处理 action,要求 reducer 纯函数且状态不可变;Vuex 则区分 state、getters、mutations、actions,并深度结合 Vue 响应式系统。Redux 更通用、生态更广;Vuex 更贴合 Vue,但新项目常使用 Pinia。

173. mobx 和 redux 有什么区别? ​

答案:

Redux 以单一 store、不可变更新和纯 reducer 为核心,状态变化通过 action 显式描述,数据流严格、可预测,适合复杂协作和时间旅行调试。MobX 通过 observable、computed、action 建立可变模型和依赖追踪,组件只在实际读取的 observable 变化时更新,代码更简洁灵活。Redux 约束强、调试清晰;MobX 上手快,但隐式依赖和可变状态需要更严格的工程规范。

174. Redux 状态管理器和变量挂载到 window 中有什么区别? ​

答案:

Redux store 是受约束、可订阅、可追踪的状态容器:状态通过 action 和 reducer 更新,具备单向数据流、时间线调试、持久化和中间件扩展能力。挂到 window 的变量只是全局可变数据,任意代码都能直接读写,缺少变更通知、边界和历史记录,容易产生隐式耦合。小型脚本可用全局变量,组件应用的共享状态通常应使用 store。

175. Redux 中异步的请求怎么处理 ​

答案:

Redux 本身只处理同步 action,异步请求通常交给中间件。常见做法是 redux-thunk:dispatch 一个函数,在函数中请求数据,再 dispatch pending、fulfilled、rejected action;也可用 Redux Toolkit 的 createAsyncThunk,或使用 saga、observable 等方案。请求状态、错误和取消逻辑应进入 reducer,组件只负责 dispatch 和展示,避免把异步副作用写进 reducer。

176. 如果使用Vue3.0实现一个 Modal,你会怎么进行设计? ​

答案:

Modal 应设计为受控组件:由父层通过 open 控制显示,通过 onClose 通知关闭,内容用插槽或 children 注入,并处理遮罩点击、Esc、焦点、滚动锁定、层级和 Teleport。Vue 3 核心写法如下:

vue
<Teleport to="body"><div v-if="open" class="mask" @click.self="emit('close')"><section role="dialog" aria-modal="true"><slot /></section></div></Teleport>

真实项目还应在打开时聚焦首个控件,关闭时恢复焦点,并清理键盘监听。

177. 使用 useState (const [test, setTest] = useState([]))时,为什么连续调用 setTest({...test, newValue}) 会出现值的丢失? ​

答案:

连续调用 setTest({...test, newValue}) 使用的都是同一次渲染中的旧 test。React 会批量更新,后一次更新会覆盖前一次,于是出现丢值;而且示例若 test 是数组,正确的扩展应是 [...test, newValue]。应使用函数式更新读取最新状态:setTest(prev => [...prev, newValue])。函数式写法能正确合并连续更新,也适用于并发渲染。

178. 说说对受控组件和非受控组件的理解,以及应用场景? ​

答案:

受控组件的值由 React state 驱动,输入通过 value 和 onChange 回写,校验、联动和提交时的数据都集中可控,适合复杂表单;非受控组件让 DOM 自己保存值,通过 ref 或 FormData 读取,代码简单且适合文件输入、与非 React 插件集成或只需提交一次的表单。两者不应在同一生命周期内随意切换,否则会出现受控/非受控警告。

179. 说说React render方法的原理?在什么时候会被触发? ​

答案:

React 的 render 阶段根据当前 props、state 和 context 执行组件函数或类组件的 render,生成新的 React element 树;随后 reconciler 对比新旧树,在 commit 阶段把最小变更写入真实 DOM。render 应保持纯净,不执行副作用。它会在首次挂载、state 更新、父组件重新渲染导致 props 变化、context 变化时触发;memo、稳定 props 等可跳过无必要的子树渲染。

180. 说说React Router有几种模式,以及实现原理? ​

答案:

React Router 常见有三种模式:BrowserRouter 使用 History API 和服务器回退配置,URL 最自然;HashRouter 把路由放在 # 后,不依赖服务端重写,适合静态托管;MemoryRouter 将历史保存在内存,常用于测试、原生容器或非浏览器环境。路由变化时,Router 监听 popstate 或 hashchange,并把当前 location 放入上下文,匹配到的 Route 再渲染对应组件。

181. 说说你对React Router的理解?常用的Router组件有哪些? ​

答案:

React Router 是基于 React 的声明式路由库,用 URL 与组件树建立映射,并提供导航、嵌套路由、参数、重定向和路由级数据能力。常用组件包括 BrowserRouter、HashRouter、MemoryRouter,配置路由用 Routes/Route,导航用 Link、NavLink、Navigate,代码跳转使用 useNavigate,读取参数和当前位置使用 useParams、useLocation。不同版本 API 有差异,应以项目版本为准。

182. 你在React项目中是如何使用Redux的? 项目结构是如何划分的? ​

答案:

我会按领域拆分 Redux:store 配置根 store 和中间件,slices 或 reducers 保存各业务状态与 reducer,actions/thunks 封装更新和异步流程,selectors 封装读取逻辑,页面组件只通过 selector 读取、dispatch 事件。以 Redux Toolkit 为主,使用 configureStore、createSlice 和 createAsyncThunk,按功能切片而不是按文件类型无限拆分;局部状态仍留在组件内部。

183. 说说对Redux中间件的理解?常用的中间件有哪些?实现原理? ​

答案:

Redux 中间件位于 dispatch 与 reducer 之间,可拦截 action,做日志、异步、权限、埋点或改写 action。典型实现是三层函数:store => next => action,处理后调用 next(action) 继续传播,否则会中断。常用有 thunk、saga、logger,以及 Redux Toolkit 默认的 thunk 和检查中间件。中间件只能增强 dispatch 流程,不能绕过 reducer 直接改变 state。

184. 说说你对Redux的理解?其工作原理? ​

答案:

Redux 是独立于 UI 框架的可预测状态容器,核心是单一 store、状态只读、通过纯 reducer 根据 action 计算新状态。组件 dispatch action,middleware 处理副作用后交给 reducer,store 保存结果并通知订阅者,React-Redux 再通过 selector 触发相关组件更新。不可变更新让引用比较和时间旅行调试成立;实际项目优先使用 Redux Toolkit 减少样板代码。

185. 在react中怎么实现组件间的过渡动画? ​

答案:

可使用 React Transition Group 管理进入和离开状态,再配合 CSS transition 或 animation。核心是组件挂载时添加 enter/enter-active 类,离开时保留节点直到动画结束:

jsx
<CSSTransition in={open} timeout={200} classNames="fade" unmountOnExit><Panel /></CSSTransition>

也可使用 Framer Motion。要注意离场动画不能立即卸载、列表动画要提供稳定 key,并尊重 prefers-reduced-motion。

186. React构建组件的方式有哪些?有什么区别? ​

答案:

React 构建组件主要有函数组件和类组件。函数组件以函数返回 JSX,配合 Hooks 管理状态、副作用和复用逻辑,代码简洁,是当前推荐方式;类组件继承 React.Component,通过 state、生命周期和 this 工作,适合维护旧项目。两者都接收 props、参与 reconciliation,性能没有本质差距;新代码优先函数组件,错误边界等少数场景仍常见类组件。

187. state 和 props有什么区别? ​

答案:

props 是父组件传给子组件的只读输入,用于配置和通信,子组件不能直接修改;state 是组件自己拥有并通过 setter 更新的数据,变化会触发重新渲染。props 变化可能来自父组件,state 生命周期属于当前组件;state 可以提升为父组件后再以 props 下传。不要把 props 机械复制到 state,否则容易出现数据源不一致,只有需要独立编辑或初始化快照时才这样做。

188. 说说react中引入css的方式有哪几种?区别? ​

答案:

React 引入 CSS 常见方式有全局 CSS、CSS Modules、CSS-in-JS、预处理器和内联 style。全局 CSS 简单但容易命名冲突;Modules 通过局部类名解决隔离,适合组件样式;CSS-in-JS 可按 props 动态生成并管理主题,但有运行时或工具链成本;预处理器提供变量、嵌套和 mixin。内联 style 适合少量动态属性,却不支持伪类、媒体查询和完整级联。

189. 说说你对immutable的理解?如何应用在react项目中? ​

答案:

Immutable 强调数据不可原地修改,每次变更生成新引用,从而可用引用比较快速判断是否更新,也便于撤销、时间旅行和并发渲染。React 项目中可直接用展开运算符和数组方法复制更新,复杂嵌套可使用 Immer;旧项目也可能使用 Immutable.js。它不是绝对禁止可变对象,而是要求状态边界内遵守不可变更新,避免直接修改 state、复用旧引用导致组件不刷新。

190. 说说React Jsx转换成真实DOM过程? ​

答案:

JSX 会先经 Babel 转换为 React.createElement(新 JSX transform 则调用 jsx runtime),生成包含 type、props、key 等信息的 React element,也就是轻量的虚拟树。React 首次渲染时创建真实 DOM;后续更新会重新生成 element 树,由 reconciler 按类型和 key 对比新旧树,只把必要变更提交到 DOM。函数组件会被执行得到子树,事件等 props 在提交阶段绑定。

191. 说说你在React项目是如何捕获错误的? ​

答案:

React 项目通常用错误边界捕获渲染阶段、生命周期和构造阶段的子树错误:类组件实现 static getDerivedStateFromError 展示降级 UI,再用 componentDidCatch 上报日志,并在页面边界、路由边界等位置分层包裹。它捕获不了事件处理器、异步回调、服务端渲染和错误边界自身的错误,这些分别用 try/catch、Promise 错误处理、服务端监控等方式兜底。

192. 说说React服务端渲染怎么做?原理是什么? ​

答案:

React SSR 是在服务端执行组件树,生成初始 HTML 后返回浏览器;客户端再用 hydrateRoot 复用这份 HTML 并绑定事件,这个过程叫 hydration。服务端可用 react-dom/server 的 renderToPipeableStream 或 renderToReadableStream,配合 Next.js 等框架处理路由、数据和代码拆分。SSR 改善首屏可见内容和 SEO,但要避免服务端与客户端首屏输出不一致,组件中的浏览器 API 只能在客户端 effect 中使用。

193. react中懒加载的实现原理是什么? ​

答案:

React 懒加载通常由 import()、React.lazy 和 Suspense 配合完成。打包器会把动态导入拆成独立 chunk,组件真正渲染时才请求该 chunk;Suspense 在 Promise pending 时显示 fallback,加载完成后再渲染组件,失败则应由 Error Boundary 提供降级 UI。被懒加载模块通常需要默认导出组件。

194. React中的路由懒加载是什么?原理是什么? ​

答案:

路由懒加载是按路由拆分代码,用户访问某个页面时才下载对应组件。常见写法是 const Page = lazy(() => import('./Page')),路由配置中使用 Page,外层用 Suspense 提供加载态。Webpack/Vite 将 import() 生成 chunk,并通过动态 script 或模块请求加载;网络失败需配合错误边界和重试、兜底页面。

195. React有哪些性能优化的方法? ​

答案:

主要从三方面优化:减少工作量,如虚拟列表、分页、惰性渲染和拆小组件;减少无效渲染,用 React.memo、useMemo、useCallback,并保持 props 引用稳定;缩小更新范围,合理拆分状态、避免滥用 Context。还要减少渲染中的复杂计算和副作用,使用生产构建、代码分割及图片/缓存优化。优化前应以 Profiler 和性能指标定位瓶颈,避免盲目缓存。

196. React Fiber 是如何实现更新过程可控? ​

答案:

Fiber 把一次递归、同步的协调工作拆成许多可恢复的工作单元,每个单元对应 Fiber 节点。调度器执行单元时检查剩余时间,必要时让出主线程;之后可从保存的 Fiber 状态继续,并按更新优先级调度。协调阶段可中断、重做,提交阶段仍需一次性完成 DOM 变更,从而保证提交结果一致。

197. Fiber 为什么是 React 性能的一个飞跃? ​

答案:

React 15 的 Stack Reconciler 使用同步递归,组件树较大时会长时间占用主线程,输入和绘制容易卡顿。Fiber 重写了协调器,把树节点变成可暂停、恢复和复用的工作单元,并结合优先级调度,让高优先级交互先处理。它提升的是响应性和调度能力,不是让每次更新的算法复杂度自动变低;不当的大量计算仍会卡顿。

198. 不同版本的 React 都做过哪些优化? ​

答案:

React 15 主要仍是同步 Stack Reconciler;React 16 引入 Fiber、错误边界和新的协调基础;React 16.6 增加 React.memo、lazy 与 Suspense(代码分割场景);React 16.8 增加 Hooks;React 17 调整事件委托到根节点并改善升级兼容;React 18 引入并发渲染、自动批处理、createRoot、startTransition 和流式 SSR。具体能力以使用的版本和渲染器为准。

199. React Hooks当中的useEffect是如何区分生命周期钩子的 ​

答案:

useEffect 没有直接对应某一个生命周期,而是用依赖数组和清理函数表达同步关系。组件首次提交后执行 effect;依赖变化时先执行上一次 cleanup,再执行新 effect;依赖为空通常只在挂载后执行、卸载时 cleanup;不传依赖则每次提交后执行。开发环境 Strict Mode 可能额外执行一次 setup/cleanup 以检查副作用。需要在 DOM 更新后立即读取布局时使用 useLayoutEffect。

200. 列举几个常见的 Hook? ​

答案:

常见 Hook 包括:useState 管理局部状态,函数式更新适合依赖旧值;useEffect 同步网络请求、订阅等副作用;useMemo 缓存计算值;useCallback 缓存函数引用;useRef 保存跨渲染的可变值或 DOM 引用;useContext 读取上下文;useReducer 管理复杂状态。自定义 Hook 可复用带状态的逻辑,但必须遵守只能在组件或其他 Hook 顶层调用的规则。

201. React Hooks带来了什么便利? ​

答案:

Hooks 让函数组件拥有状态、生命周期式副作用、引用和上下文能力,减少类组件中的 this、生命周期拆散和高阶组件嵌套。更重要的是,可以把相关逻辑按关注点封装成自定义 Hook,跨组件复用,而不必复用整棵 UI。它不会自动解决性能或副作用问题,仍须正确处理依赖、清理、闭包和状态不可变更新。

202. 在 React 中可以做哪些性能优化? ​

答案:

常用手段包括代码分割和路由懒加载、虚拟列表、分页与惰性渲染,减少首屏和实际 DOM 数量;用 memo、useMemo、useCallback 避免无意义渲染,但前提是比较和缓存成本值得;拆分组件和状态、稳定 key 与 props 引用、避免渲染期间重计算;再结合生产构建、资源压缩、缓存和 Profiler 定位瓶颈。

203. React中的VM 一定会提高性能吗? ​

答案:

不一定。这里的 VM 通常指虚拟 DOM:它先在内存中生成 React Element/Fiber 树,再与上次结果做协调,最后只提交必要的 DOM 变更。若更新范围小、DOM 操作昂贵,它能减少直接操作;但创建对象、diff 和提交也有成本。简单页面、频繁全量更新或写法导致大量无效重渲染时,虚拟 DOM 未必更快,性能必须通过实际测量判断。

204. 为什么React的 VM 可以提高性能? ​

答案:

虚拟 DOM 提升性能的核心不是“虚拟”本身,而是把渲染描述与真实 DOM 操作分离。状态变化后,React 生成新树,与旧树按类型、key 等规则协调,计算最小必要变更,再批量提交到 DOM,避免开发者重复执行大量无效操作。它还便于声明式编程和跨平台渲染;若更新本来很小或 diff 成本超过收益,优势就不明显。

205. react 的虚拟dom是怎么实现的? ​

答案:

React 的虚拟 DOM 并不是浏览器 DOM 的完整副本,而是由 createElement 产生的轻量 React Element 描述,更新时再由 Fiber 保存协调所需的信息。状态或 props 变化后,组件重新计算元素树,协调器把新旧树按类型和 key 比较,生成插入、删除、更新等副作用,提交阶段再执行真实 DOM 操作。React 18 中 Fiber 还承担可中断调度和优先级管理。

206. React 中的 ref 有什么用? ​

答案:

ref 用于取得 DOM 节点或组件实例,或保存不应触发重渲染的可变值。函数组件中可写 const inputRef = useRef(null),再通过 ref={inputRef} 访问 inputRef.current;也可用 forwardRef 暴露子组件能力。它适合聚焦、测量、媒体控制、保存定时器等命令式场景,不应拿来绕过 props/state 传递常规数据;更新 current 不会触发渲染。

207. 在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState 会发生什么? ​

答案:

shouldComponentUpdate 或 componentWillUpdate 中调用 setState 会再次安排更新。前者会触发新的更新判断,后者属于更新流程中的旧生命周期,容易形成递归更新、死循环或“更新期间更新”的警告;即使暂时没有死循环,也会造成不可预测的渲染顺序。应在事件处理、componentDidMount/componentDidUpdate 的明确条件分支中更新,并在后者比较前后 props/state,或改用 Hooks。

208. setState 之后发生了什么 ​

答案:

调用 setState 不会立即改写当前渲染中的 state,而是创建更新并进入队列。React 会按优先级合并或批处理更新,重新执行组件渲染,协调新旧元素树,最后在提交阶段更新 DOM 并运行相关生命周期/effect。对象形式的更新可能按批次合并,依赖旧状态时应使用 setState(prev => next);更新后的值应在回调或下一次渲染中读取。

209. setState 是同步,还是异步的? ​

答案:

不能简单概括为同步或异步。setState 是安排更新的 API,在 React 18 中,事件、Promise、定时器等来源通常都会自动批处理;React 可能延迟渲染,保证一次任务内合并多个更新。类组件可用 setState 回调观察提交后的结果;函数组件应使用下一次渲染或 effect。需要依赖旧值时使用函数式更新,紧急场景可谨慎使用 flushSync 强制同步提交。

210. React中为什么要给组件设置 key? ​

答案:

key 是同级列表元素的稳定身份标识,帮助 React 在协调时判断哪些节点是复用、插入、删除或移动,从而保留正确的组件状态并减少 DOM 操作。它必须在同级中唯一且随数据稳定,通常使用数据库 id。若使用随机值会导致每次全量卸载重建;若用下标,插入、删除、排序后身份会错位,可能复用错误状态。

211. React 组件间怎么进行通信? ​

答案:

父子组件通信通常用 props 向下传值,子组件通过父组件传入的回调向上通知;兄弟组件可把共享状态提升到最近公共父组件。跨层级少量共享数据可用 Context,复杂或跨页面状态可用 Redux、Zustand 等外部 store。也可用 URL、事件或服务层传递,但应明确数据所有权,避免滥用 Context、全局可变变量和直接修改 props。

212. React 中如果绑定事件使用匿名函数有什么影响? ​

答案:

例如 onClick={() => handle(id)} 会在每次父组件渲染时创建新的函数对象。对普通 DOM 元素通常没有正确性问题,因为 React 会更新事件引用;但传给 React.memo 或依赖引用比较的子组件时,会使 props 看起来每次都变,削弱跳过渲染的效果。只有测量确认有收益时,才用 useCallback 稳定引用;还要正确声明依赖,不能为优化而牺牲闭包正确性。

213. React 的事件代理机制和原生事件绑定混用会有什么问题? ​

答案:

混用时要特别注意事件顺序、冒泡和生命周期。原生监听器可能在 React 处理前或后执行,调用 stopPropagation 的位置不同还可能阻止另一套处理;React 17 前事件多委托到 document,版本差异会放大行为差别。若原生回调直接改 DOM,也会与 React 的声明式结果冲突。应尽量统一事件体系,必须混用时固定挂载节点,明确捕获/冒泡阶段并在 effect 清理监听器。

214. React 的事件代理机制和原生事件绑定有什么区别? ​

答案:

React 事件通常在根容器上做委托,用统一的合成事件接口把原生事件分发给组件处理;现代 React 事件委托在 React 根节点,旧版本多在 document。原生绑定则直接注册在具体 DOM 节点,依赖浏览器原生事件对象、捕获/冒泡和默认行为。React 事件便于跨浏览器和框架管理,原生事件适合第三方库或特殊底层需求,但要手动移除监听器。

215. 简述下 React 的事件代理机制? ​

答案:

React 事件代理是事件委托:React 在根容器注册少量原生监听器,事件冒泡到根节点后,根据 Fiber 树找到目标组件,再调用对应 props 处理器,并提供统一的合成事件接口。这样可减少大量 DOM 监听器,组件挂载和卸载也不必逐个注册。它仍受原生捕获、冒泡和默认行为影响;与原生监听混用时要留意注册层级、执行顺序及 stopPropagation。

答案:

<a href="/detail"> 是浏览器原生导航,默认会发起新文档请求并刷新页面,适合外部地址或需要完整加载的场景。React Router 的 <Link to="/detail"> 会拦截点击,调用 History API 修改 URL,由路由器匹配并渲染新组件,通常不刷新整页。它仍应渲染为可访问链接;外部 URL、下载、特殊鼠标操作等情况应保留原生行为。

217. 简单介绍下React中的 diff 算法 ​

答案:

React diff 是协调过程中的启发式比较,不做通用树编辑距离。根节点类型变化通常卸载旧子树并创建新树;类型相同则复用节点并更新属性;列表按 key 和位置匹配,稳定 key 能识别移动、插入和删除。整体通常接近 O(n),代价是要求开发者提供稳定 key,且不同层级的节点不会被跨层级匹配。diff 只计算更新,真实 DOM 变更在提交阶段执行。

218. React中,能否直接将 props 的值复制给 state? ​

答案:

技术上可以在初始化时把 props 作为 state 初值,但它只复制一次,后续 props 变化不会自动同步,因此常造成过期数据。若值只是展示或派生结果,应直接使用 props;若确实需要可编辑草稿,应明确受控/非受控设计,并在必要时通过 key 重置,或在比较 props 后有条件同步。不要在每次渲染中强行覆盖 state,也不要把 props 当作可变对象直接修改。

219. 简述下 React 的生命周期?每个生命周期都做了什么? ​

答案:

类组件可分为挂载、更新、卸载和错误处理。挂载阶段依次涉及 constructor、render、componentDidMount;更新阶段常见 render、componentDidUpdate,shouldComponentUpdate 可做性能判断,getDerivedStateFromProps 仅适用于少数派生状态场景;卸载时执行 componentWillUnmount 清理订阅和定时器。getSnapshotBeforeUpdate 可在 DOM 提交前读取滚动等信息并传给 didUpdate。函数组件用 render 加 effect/cleanup 表达同类逻辑;错误边界用 getDerivedStateFromError 或 componentDidCatch 捕获子树错误。

220. 如何在React中应用样式? ​

答案:

常见方式有三类:引入普通 CSS/SCSS 文件,用 className 应用类名;CSS Modules 让类名局部化,适合组件样式;CSS-in-JS 或内联 style 可根据 props 动态生成样式。style 接收 JavaScript 对象,属性写成 backgroundColor,数值多数按 px 处理。大型项目还应统一主题、命名和构建方案;避免把复杂静态样式都放进内联对象,以免每次渲染创建新对象并降低可维护性。

221. 什么是 React? ​

答案:

React 是用于构建用户界面的 JavaScript 库,核心思想是组件化、声明式渲染和单向数据流。组件根据 props 与 state 描述 UI,状态变化后 React 通过 Element/Fiber 协调新旧结果,再把必要变更提交到 DOM。它不是完整的后端或全套应用框架,路由、请求、状态管理等通常按项目需要组合。函数组件与 Hooks 是现代 React 的主要写法。

222. React.PureComponent 和 React.Component 有什么区别? ​

答案:

React.Component 默认不会因 props/state 的浅比较而跳过更新,需自行实现 shouldComponentUpdate。React.PureComponent 内置对 props 和 state 的浅比较,引用不变时通常跳过重新渲染。它只比较第一层,直接修改嵌套对象或每次创建新对象会导致漏更新或失去优化;因此应坚持不可变更新,并确认组件是纯的。PureComponent 也不能阻止 context 等外部因素带来的更新。

223. 为什么不能用数组下标来作为react组件中的key? ​

答案:

数组下标只表示当前排列位置,不代表数据身份。若在头部插入、删除或排序,后续元素的下标都变化,React 会把旧组件状态错误地复用到别的数据上,并产生更多更新。应使用数据中稳定且同级唯一的 id。只有列表永远不排序、不插入删除,且条目没有本地状态时,下标作为 key 才通常不会造成可见问题,但仍不如稳定 id 清晰可靠。

224. React Fiber是什么? ​

答案:

Fiber 是 React 16 重写后的协调架构,也是表示组件工作单元的数据结构。每个 Fiber 保存组件类型、父子兄弟关系、待处理 props/state、更新队列和副作用等信息。协调过程可以把大任务拆开,按优先级执行,在合适时机暂停、恢复或丢弃未提交工作;完成后进入提交阶段统一修改 DOM。Fiber 改善了主线程响应性和并发调度,不等于所有渲染都自动更快。

225. 使用React Hooks有什么优势? ​

答案:

Hooks 让函数组件也能使用状态、生命周期相关能力和上下文,减少类组件的样板代码。它把可复用逻辑抽成自定义 Hook,复用的是状态逻辑而不是组件结构,通常比 HOC 或 render props 更直观。使用时必须遵守只在组件或自定义 Hook 顶层调用、顺序稳定等规则;它不会自动解决状态共享或副作用依赖问题。

226. 为什么不能直接使用 this.state 改变数据? ​

答案:

直接修改 this.state 只是改了对象引用,React 不会把它纳入更新调度,也不能可靠触发重新渲染,还可能绕过批量更新造成状态覆盖。应使用 this.setState,它会合并更新、调度渲染并维护生命周期。新状态依赖旧状态时要用函数写法:this.setState(prev => ({count: prev.count + 1}));对象或数组也应先创建新引用。

227. 什么是JSX? ​

答案:

JSX 是 JavaScript 的语法扩展,用类似 HTML 的写法描述 React 元素树。它会经 Babel 等工具编译为 React.createElement 或新的 JSX runtime 调用,因此不是浏览器原生 HTML,也不是模板语言。表达式需放在 {} 中,属性名遵循 JavaScript 规则,例如 className;组件标签必须大写,并且通常要求返回单一根节点或 Fragment。

228. 什么是受控组件? ​

答案:

受控组件的值由 React state 或父组件 props 统一管理,表单元素通过 value/checked 展示状态,再在 onChange 中更新状态。例如 <input value={name} onChange={e => setName(e.target.value)} />。这样校验、格式化、提交和联动都有单一数据源,行为可预测;若不需要实时控制,可使用 defaultValue 配合 ref 的非受控组件。

229. constructor中super与props参数一起使用的目的是什么? ​

答案:

子类构造函数中必须先调用 super(),才能使用 this,因为它负责初始化父类 React.Component。把 props 传给 super(props),才能在 constructor 内通过 this.props 访问属性;不传时,构造函数参数 props 仍可用,但 this.props 通常未初始化。若没有自定义 constructor,则 React 会代为处理,函数组件也不存在这个问题。

230. 什么是高阶组件? ​

答案:

高阶组件(HOC)是接收组件并返回新组件的函数,例如 const Enhanced = withAuth(Page)。它通过包装和组合复用权限校验、数据获取、埋点等横切逻辑,不应修改传入组件本身,通常要透传未使用的 props。缺点是可能形成包装层、props 命名冲突和调试困难;现代 React 更常优先使用自定义 Hook,HOC 仍适合统一增强组件。

231. React中的类组件和函数组件之间有什么区别? ​

答案:

类组件通过继承 React.Component 并实现 render,可使用 this.state、生命周期和实例方法;函数组件是接收 props 并返回元素的普通函数。React 16.8 后函数组件可用 Hooks 管理状态、副作用和上下文,代码通常更简洁、逻辑复用更自然。两者都能接收 props、参与渲染,性能没有由组件形式决定的绝对差异,关键在状态设计和渲染范围。

232. 什么是虚拟DOM? ​

答案:

虚拟 DOM 是用 JavaScript 对象描述界面节点及其属性的一种内存表示,不是浏览器中的真实 DOM。状态变化后,框架重新生成虚拟树,通过 diff/协调比较新旧树,只把必要的属性和节点变更提交给真实 DOM,从而减少直接 DOM 操作并保持声明式开发。它不保证任何场景都更快,首次渲染、复杂 diff 或不当更新仍可能有成本。

233. Vue 3.0中Treeshaking特性是什么,并举例进行说明? ​

答案:

Vue 3 的 Tree Shaking 会让打包器根据 ES Module 的静态导入分析,只保留实际使用的 API,未使用的代码在生产构建中被删除,从而减小体积。例如只导入 ref:import { ref } from 'vue',未使用的 reactive、computed 等不会进入最终包。它依赖静态 ESM 导入和生产压缩配置,动态导入或副作用代码不能随意删除。

234. Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同? ​

答案:

Options API 按 data、computed、methods、watch 等选项组织组件,易上手,但一个业务逻辑常分散在多个选项中。Composition API 以 setup 和组合函数为中心,按功能聚合状态、计算、监听和副作用,更利于复用、TypeScript 推导及大型组件维护。它们可以混用;Composition API 不是强制替代,简单组件使用 Options API 仍然清晰。

235. Vue3.0里为什么要用 Proxy API 替代 defineProperty API ? ​

答案:

Object.defineProperty 需要递归遍历对象并为每个属性建立 getter/setter,新增或删除属性难以自动侦测,数组索引和长度也需额外处理。Proxy 代理整个对象,可统一拦截读取、写入、删除、in 等操作,天然支持新增属性、数组和 Map/Set 等集合,初始化和维护更简单。代价是旧浏览器兼容性要求更高,且仍需注意不可变数据与副作用设计。

236. Vue3.0 性能提升主要是通过哪几方面体现的? ​

答案:

Vue 3 的性能优化主要包括:编译器静态分析并提升静态节点、缓存事件处理器、生成更精确的 patch 标记;运行时采用更高效的响应式实现和组件更新策略;源码使用模块化设计,配合 Tree Shaking 减少包体积;同时改进 SSR、异步组件和列表更新。实际收益取决于模板结构、更新范围和构建配置,不能简单等同于所有场景都更快。

237. Vue3.0的设计目标是什么?做了哪些优化? ​

答案:

Vue 3 的目标是提高运行时与编译性能、改善 TypeScript 支持、降低包体积,并让大型项目的逻辑复用和维护更自然。主要优化包括 Proxy 响应式、Composition API、模块化 Tree Shaking、编译时静态提升与 patch flag、改进的组件更新和 SSR。迁移时还要关注全局 API、生命周期、事件 API 及部分插件生态变化,不能只替换版本号。

238. Vue3有了解过吗?能说说跟Vue2的区别吗? ​

答案:

Vue 3 使用 Proxy 响应式,支持新增/删除属性和数组、集合类型,Vue 2 基于 defineProperty,需要初始化时递归劫持,存在检测盲区。Vue 3 提供 Composition API,更适合逻辑复用和 TypeScript;编译器有静态提升、patch flag,包体积和更新效率更好。两者 API 大体兼容,但 Vue 3 的全局 API、过滤器、事件总线等用法及生态配置有所变化。

239. 你是怎么处理vue项目中的错误的? ​

答案:

我会按层次处理:接口层用 axios 拦截器统一处理网络错误、超时、鉴权失效和业务码;组件层用 try/catch、错误状态和友好提示处理可恢复错误;Vue 全局用 errorHandler 捕获渲染和生命周期异常,必要时配合错误边界组件;窗口级监听 error 与 unhandledrejection,上报日志和上下文。生产环境还要区分用户提示、重试策略与敏感信息脱敏。

240. Vue项目如何进行部署?是否有遇到部署服务器后刷新404问题? ​

答案:

部署通常是 npm run build 生成 dist,将静态文件交给 Nginx、CDN 或对象存储,并配置正确的资源基路径、环境变量、缓存策略和 HTTPS。使用 history 路由时刷新 404,是因为服务器把 /user/1 当成真实文件查找;Nginx 应将未知路径回退到 index.html,例如 try_files $uri $uri/ /index.html。若无法配置回退,可改用 hash 路由。

241. Vue项目中如何解决跨域问题? ​

答案:

开发环境优先使用 Vite/Webpack dev server 的 proxy,把 /api 转发到后端;浏览器只访问同源开发服务器,代理再转发,因此绕过跨域限制。生产环境通常由 Nginx 反向代理统一域名,或让后端正确配置 CORS 的 Origin、方法、请求头和凭据策略。JSONP 只适用于 GET,不能作为通用方案;不要用前端关闭浏览器安全策略掩盖配置问题。

242. Vue怎么实现权限管理?控制到按钮级别的权限怎么做? ​

答案:

权限管理应先在路由守卫中校验登录态和菜单/路由权限,再在后端接口层做最终授权,前端隐藏按钮不能替代服务端鉴权。按钮级权限可将用户权限码放入 store,封装 v-permission 自定义指令或权限组件,在挂载前判断并移除无权元素;也可禁用而非隐藏。权限变化、刷新恢复、无权限兜底页和动态路由销毁都要处理。

243. 大型项目中,Vue项目怎么划分结构和划分组件比较合理呢? ​

答案:

大型 Vue 项目可按业务域划分目录,而不是按文件类型堆放:页面负责组合,业务组件负责视图,composables 负责可复用逻辑,api 负责请求,store 按领域拆分,公共组件和工具保持稳定边界。组件应单一职责,父子只通过 props/emit 通信,跨域状态才进入 store。路由、权限、接口模型和错误处理统一约定,并用懒加载控制首屏体积。

244. SSR是什么?Vue中怎么实现? ​

答案:

SSR(服务端渲染)是在服务器根据请求生成首屏 HTML,再由客户端 hydrate 接管交互。Vue 3 通常使用 createSSRApp、@vue/server-renderer 和 Nuxt 实现;服务端输出 HTML,客户端加载同构代码并完成 hydration。它能改善首屏速度和 SEO,但增加服务器成本、状态隔离和部署复杂度。服务端与客户端渲染结果必须一致,浏览器专属 API 不能直接在服务端执行。

245. Vue项目中有封装过axios吗?怎么封装的? ​

答案:

我会封装一个 axios 实例,集中设置 baseURL、超时、序列化和公共请求头;请求拦截器注入 token,响应拦截器统一解包业务数据、处理 401、网络错误和取消请求,并保留原始错误供页面按场景提示。按业务域拆分 API 文件,避免在组件中直接拼 URL。重试、幂等性、刷新 token 和文件下载要明确边界,不能无条件重试写操作。

246. 说说vue中的diff算法 ​

答案:

Vue 的 diff 是在响应式更新后比较新旧虚拟节点,尽量复用已有 DOM 和组件实例,只执行必要的新增、删除、移动和属性更新。Vue 3 编译器会用 patch flag 缩小比较范围;同层子节点通常采用双端比较并结合 key 做最长递增子序列优化,以减少移动。diff 不是任意节点全局匹配,跨层级结构变化通常直接替换,因此应使用稳定且唯一的业务 key。

247. 什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路 ​

答案:

可以把虚拟 DOM 看成 {type, props, children, key} 的树。渲染函数先由数据生成 vnode,再用 mount(vnode, container) 创建元素、设置属性并递归挂载子节点;更新时生成新树,patch(oldVNode, newVNode, el) 比较类型、props 和子节点,按需更新真实 DOM。子列表应按 key 建索引并复用节点。基础实现能说明原理,但生产框架还需处理事件、组件、Fragment、文本和异步更新。

248. 自定义指令是什么?有哪些应用场景? ​

答案:

自定义指令是对 DOM 进行底层操作的复用机制,Vue 3 可通过 app.directive 或组件内 directives 注册,并在 mounted、updated、unmounted 等钩子中处理。常见场景有自动聚焦、权限控制、点击外部关闭、拖拽和第三方 DOM 插件。它适合与具体元素强相关的行为;能用组件或 composable 表达的业务逻辑不应塞进指令,并要在卸载时清理监听器。

249. Vue常用的修饰符有哪些?分别有什么应用场景? ​

答案:

常用修饰符包括:.stop 阻止冒泡,.prevent 阻止默认行为,.self 仅事件目标是自身时触发,.once 只触发一次;按键修饰符如 .enter 限定键盘事件;.capture、.passive 控制监听阶段和滚动性能;表单的 .trim、.number、.lazy 分别处理去空格、转数字和失焦同步。应按语义使用,.passive 不能与 preventDefault 同时依赖。

250. 说说你对Vue中 keep-alive 的理解 ​

答案:

keep-alive 用于缓存动态组件或路由组件实例,切换时不销毁,而是触发 activated/deactivated,可保留表单、滚动位置和组件状态。它支持 include、exclude 和 max 控制缓存范围与数量,通常结合组件 name 使用。缓存会占用内存,数据过期或用户切换账号时应主动清理;不要把所有页面都缓存,否则可能展示过期状态和增加资源占用。

251. 说说vue中,key的原理 ​

答案:

key 是虚拟节点的稳定身份标识,diff 列表时 Vue 会据此判断节点是复用、移动还是新建,从而保留正确的 DOM 状态和组件实例。应使用唯一、稳定、与数据绑定的业务 ID,例如 item.id;不建议用数组索引,尤其是列表会插入、删除或排序时,否则可能出现输入框值错位、状态串行。key 改变也可刻意强制组件重新创建。

252. Vue.observable是什么? ​

答案:

Vue 2 的 Vue.observable 会把对象转换为响应式对象,可用于在简单场景下共享状态,组件读取它时能建立依赖并在修改后更新。它适合轻量、低复杂度的跨组件状态,不提供 Vuex 的模块、提交追踪和严格约束。Vue 3 中通常直接使用 reactive,并通过 composable 或 Pinia 管理共享状态;共享对象仍应明确修改入口,避免任意组件互相改写。

253. 说说你对slot的理解?slot使用场景有哪些? ​

答案:

slot 是父组件向子组件传入模板内容的插槽机制,子组件决定布局和插入位置,父组件决定具体内容。默认插槽适合单一内容,具名插槽适合 header、footer 等多个区域,作用域插槽由子组件向插槽模板暴露数据。常见场景是通用弹窗、表格列、布局和可配置列表。插槽解决的是结构定制,不应替代 props 传递数据或 emit 触发事件。

254. 说说你对vue的mixin的理解,以及有哪些应用场景? ​

答案:

mixin 会把一组组件选项混入多个组件,常用于复用生命周期、方法、计算属性或数据。它能快速抽取公共逻辑,但来源不透明,容易发生命名冲突、数据合并规则复杂和逻辑耦合,组件依赖也难追踪。Vue 3 更推荐 composable:显式导入并按功能组织响应式状态。确需使用时应保持 mixin 小而稳定,避免修改宿主组件私有状态,并明确覆盖优先级。

255. Vue中的$nextTick有什么作用? ​

答案:

$nextTick 用于等待 Vue 完成当前批量异步更新并把最新 DOM 提交后再执行回调或继续代码。修改响应式数据后立即读取 DOM 可能仍是旧值,此时可写 await nextTick() 或 this.$nextTick(() => {})。它依赖更新队列合并同一轮变更,适合读取尺寸、聚焦和初始化第三方 DOM;它不等待网络请求,也不应作为随意强制刷新的手段。

256. Vue组件间通信方式都有哪些? ​

答案:

父子组件通常用 props 下传、$emit 上报;父组件确需调用子组件公开方法可用 ref,但应少用。跨多层可用 provide/inject 或 $attrs/$listeners(Vue 2),兄弟组件可提升状态到共同父组件,复杂共享状态使用 Pinia/Vuex。EventBus 能实现任意组件事件,但隐式依赖、销毁监听和调试成本高,通常不作为首选。通信方式应匹配关系,避免直接改 props 或滥用 $parent。

257. Vue中组件和插件有什么区别? ​

答案:

组件是构成应用的业务单元,通常封装模板、状态、逻辑和样式,可全局或局部注册并在页面中复用。插件是扩展 Vue 能力的机制,通过 install 安装,可注册全局方法、指令、组件、混入或实例属性。组件服务于具体界面,插件服务于框架级能力,Vue.use 还会避免重复安装。

258. Vue中给对象添加新属性时,界面不刷新怎么办? ​

答案:

Vue 2 用 Object.defineProperty 预先拦截已有属性,直接新增属性不会被观测,因此视图不更新。应使用 Vue.set(obj, key, value) 或 this.$set;批量修改可用 Object.assign({}, oldObj, additions) 替换整个对象。Vue 3 基于 Proxy,新增属性可被自动追踪;$forceUpdate 只是兜底,不应作为常规方案。

259. 为什么Vue中的data属性是一个函数而不是一个对象? ​

答案:

组件可能被创建出多个实例,data 若直接是对象,所有实例会共享同一引用,一个实例修改数据会污染其他实例。定义为函数后,Vue 会在创建每个实例时调用它并取得新的对象,从而隔离状态。根 Vue 实例通常是单例,所以 data 可以写对象;组件的 data 必须返回对象的函数。

260. SPA(单页应用)首屏加载速度慢怎么解决? ​

答案:

先用 Lighthouse、Performance 和网络面板定位瓶颈,再优化:路由和组件懒加载、代码分割与 Tree Shaking,依赖按需引入,压缩并缓存静态资源,启用 Brotli/Gzip,优化图片并使用 CDN,减少主线程阻塞。接口可并行或预取;对首屏要求高时采用 SSR/SSG,并避免无必要的同步脚本和重复请求。

261. 为什么Vue中的v-if和v-for不建议一起用? ​

答案:

在同一节点同时使用 v-if 和 v-for 时,Vue 2 中 v-for 的优先级更高,会先遍历再判断,导致条件判断重复执行、表达意图不清,也可能产生无效遍历;Vue 3 的优先级相反,行为又不同。应把过滤结果写成 computed,再对结果 v-for;若是整组开关,则把 v-if 放在外层 template 或容器上。

262. 说说你对Vue生命周期的理解 ​

答案:

生命周期是组件从创建、挂载、更新到销毁各阶段可插入的钩子。Vue 2 常见顺序为 beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed;Vue 3 对应 beforeUnmount、unmounted。created 可访问响应式数据但还没有 DOM,mounted 后才能操作已挂载 DOM;请求和订阅要配对清理,避免在销毁后继续更新。

263. Vue实例挂载的过程中发生了什么? ​

答案:

new Vue 会执行 _init:合并配置,初始化代理、生命周期、事件和渲染能力,触发 beforeCreate;随后初始化 props、methods、data、computed、watch,并建立响应式系统,触发 created。若配置了 el,再编译模板或使用 render 函数生成 VNode,创建渲染 Watcher,执行 patch 将 VNode 转成真实 DOM,完成挂载并触发 beforeMount、mounted。

264. Vue中的 v-show 和 v-if 有什么区别 ​

答案:

v-if 是惰性条件渲染,条件为假时不创建 DOM,切换会创建或销毁节点及组件,初始开销较小但切换成本较高;v-show 始终渲染并保留节点,只切换 CSS 的 display,初始开销较大但切换便宜。组件或内容很少切换用 v-if,频繁显示隐藏用 v-show;v-show 不能用于 template,也不能用于需要销毁状态的场景。

265. 谈谈对Vue中双向绑定的理解 ​

答案:

Vue 所谓双向绑定,是数据到视图与视图到数据两条单向同步链路的组合,并非数据和 DOM 直接互相修改。响应式系统在读取数据时收集组件或 watcher 依赖,数据变更后通知依赖重新渲染;表单的 v-model 则把 value 与 input/change 事件封装起来,输入事件更新状态,状态变化再更新控件。

266. 说说你对vue的理解? ​

答案:

Vue 是渐进式、组件化的前端框架,核心负责声明式渲染和响应式更新,可按需组合路由、状态管理及构建工具,便于接入已有项目。开发者描述状态与模板,Vue 通过响应式系统收集依赖,在数据变化时触发组件更新,再比较新旧虚拟 DOM 并最小化修改真实 DOM。Vue 2 主要基于 defineProperty,Vue 3 使用 Proxy。

基于 VitePress 构建的个人学习文档知识库