快速入门vue3与常见面试题

vue 基础

1. 声明式渲染

  • 模板语法{{ }} 文本插值、v-html 输出 HTML

  • 指令

    • 内置指令:v-bindv-onv-modelv-if/v-else-if/v-elsev-showv-forv-prev-cloakv-once
    • 自定义指令:全局 Vue.directive(Vue 2)/ app.directive(Vue 3),局部 directives 选项

2. 响应式系统

2.0 数据劫持

  • 遍历 data 对象,对每个属性递归调用 defineReactive,为每个属性创建 Dep(依赖收集器)。

  • 每个属性对应一个 Watcher(观察者),在渲染时收集依赖。

  • Vue 2 :基于 Object.defineProperty,递归遍历对象属性,无法检测

    • 对象属性的添加/删除(需用 Vue.set / this.$set
    • 无法直接通过索引修改数组(arr[0] = xx 不触发更新,需用变异方法如 push, splice
    • 初始化时需要递归遍历所有深层属性,性能差。
  • Vue 3 :基于 Proxy,可监听动态添加属性、数组索引修改,性能更优,支持 MapSet 等原生集合的响应式

2.1 响应式核心原理

Vue 的响应式系统通过 数据劫持 + 依赖收集 + 派发更新 实现数据与视图的自动同步。

步骤 说明
数据劫持 对组件的 data 进行代理,拦截属性的读取和修改操作。
依赖收集 当渲染函数、计算属性或侦听器执行时,会读取响应式数据,此时将当前活跃的 Watcher(观察者) 添加到数据的依赖列表(Dep)中。
派发更新 当数据被修改时,Dep 通知所有依赖的 Watcher 执行更新。
异步更新队列 Watcher 不会立即触发 DOM 更新,而是将自己推入一个队列,在下一个事件循环的 microtask 中统一执行,并通过 nextTick 提供更新后的回调。

2.2 Vue 2 与 Vue 3 响应式实现对比

特性 Vue 2 (Object.defineProperty) Vue 3 (Proxy)
监听对象属性新增/删除 不支持,需 $set / $delete 支持,原生拦截
数组索引/长度变更 不支持,需用变异方法或 $set 支持,可直接赋值修改
初始化性能 递归遍历所有属性,数据大时较慢 懒代理,访问时才递归,初始更快
代理对象类型 只能代理普通对象 可代理数组、Map、Set、WeakMap、WeakSet
需要额外 API Vue.setVue.delete、数组变异方法 无需额外 API,直接使用原生操作
依赖收集粒度 属性级别 属性级别(但基于 Proxy 实现更简洁)
TypeScript 支持 较弱,需额外装饰器或声明 原生 TS 支持更好,reactive 返回代理类型

2.3 虚拟 DOM 与 Diff 算法

当响应式数据变化触发异步更新队列后,Vue 不会直接操作真实 DOM,而是先更新 虚拟 DOM(VNode) ,再通过 Diff 算法 找出最小变化,最后批量更新到真实 DOM 上。

2.3.1 为什么需要虚拟 DOM?
  • 真实 DOM 操作代价昂贵,频繁操作易造成性能瓶颈。
  • 虚拟 DOM 是 JS 对象,操作成本低,可在内存中完成比对。
  • Diff 算法可以复用尽可能多的真实 DOM 节点,减少重排重绘。
2.3.2 Diff 流程(以 Vue 2 / Vue 3 为例)
阶段 说明
新旧 VNode 树 组件重新渲染时,生成新的 VNode 树,与上一次渲染的旧 VNode 树进行比对。
同层比较 只对比同一层级的节点,不跨层比较,时间复杂度从 O(n³) 降为 O(n)。
节点类型判断 若新旧节点类型不同(如 divp),则直接替换整个子树(销毁旧节点,创建新节点)。
相同节点复用 若类型相同且 key 相同,则尝试复用旧 DOM,仅更新变化的属性(class、style、事件等)。
子节点 Diff 对子节点数组进行差异比对,Vue 2 使用双端比较算法(首尾交叉对比),Vue 3 增加了最长递增子序列优化。
2.3.3 Vue 3 Diff 优化亮点
优化点 说明
静态标记(PatchFlags) 编译阶段标记节点的动态属性(如 TEXTCLASSSTYLE),Diff 时只对比带标记的部分,跳过静态内容。
静态提升 将不会变化的静态节点提升到渲染函数外部,避免每次渲染都重新创建。
缓存事件处理函数 避免内联函数导致的子组件不必要更新。
最长递增子序列 在移动子节点时,寻找最长递增子序列,最小化 DOM 移动次数。

2.4 完整更新流程(响应式 → Diff → DOM 更新)

bash 复制代码
用户修改数据
    ↓
触发响应式 setter / Proxy trap
    ↓
Dep 通知相关 Watcher
    ↓
Watcher 将自己推入异步更新队列
    ↓
nextTick 时清空队列,执行组件更新函数
    ↓
生成新 VNode 树,与旧 VNode 树进行 Diff
    ↓
根据 Diff 结果生成 DOM 操作补丁(Patch)
    ↓
批量更新真实 DOM
    ↓
触发 updated 生命周期钩子

总结

Vue 2 通过 Object.defineProperty 劫持对象属性的 getter/setter 来实现响应式,但存在局限性,比如无法监听动态添加的属性,需要通过 Vue.set 处理。Vue 3 改用 Proxy,可以代理整个对象,支持多种操作拦截,解决了上述问题,同时性能更优。响应式核心是依赖收集和派发更新,在 getter 中收集依赖,在 setter 中触发更新,并通过异步队列实现批量更新

3. vue3 API

API 说明
ref() 声明任意类型的响应式数据,需通过 .value 访问。
reactive() 声明对象/数组类型的响应式数据,可直接访问属性。
readonly() 接收一个响应式对象或普通对象,返回其只读代理,不可修改。
shallowRef() 声明浅层响应式数据,只有 .value 的访问是响应式的,内部嵌套对象不会变为响应式。
shallowReactive() 声明浅层响应式对象,只有顶层属性是响应式的,嵌套对象不会被转换。
shallowReadonly() 声明浅层只读对象,顶层属性只读,嵌套对象不会被转换且不具响应性。
computed() 定义计算属性,基于响应式依赖缓存结果。
watch() 监听特定数据源,在数据变化时执行副作用。
watchEffect() 自动追踪其内部使用的响应式数据,并在数据变化时立即重新运行。
onMounted(), onUpdated(), onUnmounted() 组件生命周期不同阶段执行的钩子函数,用法与选项式 API 对应。
provide(), inject() 用于跨层级组件通信,祖先组件提供数据,后代组件注入使用。
toRefs(), toRef(), isRef(), unref() 用于处理 ref / reactive 对象的辅助函数,帮助进行响应式转换和判断。
defineProps(), defineEmits() <script setup> 中声明组件的 propsemits,享受完整类型推导。
defineExpose() <script setup> 中声明当前组件暴露给父组件的属性或方法。
defineOptions() <script setup> 中声明组件名 (name) 或 inheritAttrs 等选项。

内置组件

组件 说明
<component> 用于动态渲染不同组件的"元组件",通过 is 属性决定
<transition> 为单个元素或组件添加进入/离开过渡动画
<transition-group> 为列表中的多个元素或组件添加过渡动画
<keep-alive> 缓存动态组件,避免重复渲染和状态丢失
<teleport> 将组件模板的一部分渲染到 DOM 树中的指定位置
<suspense> 管理异步组件或依赖异步数据的组件,在等待时显示后备内容
<slot> 作为组件模板中的插槽出口,接收父组件分发的内容

内置指令

指令 说明
v-model 在表单元素或组件上创建双向绑定。
v-if, v-else-if, v-else 条件渲染,为 false 时不渲染元素。
v-show 条件渲染,通过 CSS 的 display 属性切换。
v-for 基于源数据多次渲染元素或模板块。
v-on (@) 绑定事件监听器。
v-bind (:) 动态地绑定一个或多个属性。
v-slot (#) 用于声明具名插槽或作用域插槽。

4. 生命周期钩子

阶段 Vue 2 钩子 Vue 3 钩子(Options API) Vue 3 钩子(Composition API)
初始化 beforeCreate, created setup() 代替 beforeCreate/created
挂载 beforeMount, mounted onBeforeMount, onMounted
更新 beforeUpdate, updated onBeforeUpdate, onUpdated
卸载 beforeDestroy, destroyed beforeUnmount, unmounted onBeforeUnmount, onUnmounted
错误捕获 errorCaptured onErrorCaptured
其他 activated, deactivated(keep-alive) onActivated, onDeactivated
调试 renderTracked, renderTriggered(开发) onRenderTracked, onRenderTriggered

5. 插槽

  • 默认插槽、具名插槽、作用域插槽(slot-scope in Vue 2,v-slot in Vue 2.6+ & Vue 3)
  • Vue 3 中 v-slot 统一为指令语法,slotslot-scope 被废弃

6. 混入(Mixin)

  • 全局混入、局部混入
  • 合并策略:数据递归合并,同名钩子合并为数组,方法/组件/指令等直接覆盖
  • 缺点:命名冲突、隐式依赖、代码不直观 → 推荐组合式 API 替代

7. 自定义指令

  • 钩子函数:

    • Vue 2:bind, inserted, update, componentUpdated, unbind
    • Vue 3:beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted
  • 参数:el, binding, vnode, prevVnode

8. 过滤器(Filters)

  • Vue 2 支持模板内过滤器({{ msg | filter }})及全局/局部定义
  • Vue 3 中移除,推荐用计算属性或方法替代

9. 动画与过渡

  • <transition> 单元素过渡
  • <transition-group> 多元素/列表过渡
  • 类名约定:v-enter-from/v-enter-to 等(Vue 3 命名变化)
  • JavaScript 钩子:@before-enter, @enter, @after-enter

10、组件通信方式(详细对比)

方式 Vue 2 Vue 3
props / $emit 支持 支持,emit 需在 setup 中声明
v-model 单个,value + input 可多个,modelValue + update:modelValue,支持自定义修饰符
refs/refs / refs/parent / $children $children 存在 移除 $children,推荐 ref + $parent 或组合式 API
provide / inject 默认非响应式,可传递响应式对象 支持响应式传递,可提供 ref/reactive
event bus new Vue() 作为总线 推荐用 mitt 等第三方库
Vuex Vuex 3 Vuex 4 / Pinia
slot 作用域 slot-scope v-slot 统一语法
组合式 API 可直接使用 ref 传递,逻辑复用更灵活

11、Vue Router 对比(3.x vs 4.x)

特性 Vue Router 3(Vue 2) Vue Router 4(Vue 3)
创建方式 new VueRouter(...) createRouter({ ... })
模式 mode: 'history' / 'hash' history: createWebHistory() / createWebHashHistory()
路由守卫 beforeEach / beforeResolve / afterEach 同,但支持组合式 API 中的 onBeforeRouteUpdate
路由元信息 meta
动态路由 addRoutes addRoute,且支持动态删除
导航故障 NavigationFailureType 更完善的类型
组合式 API 不支持 useRouteruseRoute

12、状态管理:Vuex vs Pinia

特性 Vuex 3/4 Pinia
设计哲学 Flux 正统继承者:严格单向数据流 View → Action → Mutation → State → View 拥抱 Composition API:去掉 mutations,actions 可直接修改 state , ---- API: actionsstategetters
API 风格 Options API 风格(state, mutations, actions, getters) 同时支持 Options 和 Composition API 风格,更接近普通 JS 对象
状态修改方式 必须通过 commit(mutation) 同步修改,异步操作需在 action 中 commit 可以直接在 action 中 this.count++store.count++,同步/异步统一处理
Mutations 必须有,且必须是同步函数 没有 mutations,所有修改统一写在 actions 中
样板代码量 较多(需要定义 mutations + actions + 辅助函数) 减少约 40% 样板代码,更简洁直观
TypeScript 支持 需要额外定义 State 接口,借助 createStore<State> 或装饰器,组件中还需标注类型 原生 TS 支持,自动推导,几乎没有模板代码;Setup Store 风格体验更佳
模块系统 modules 嵌套,有命名空间概念,默认所有 action/mutation 全局,需手动开启 namespaced: true 每个 defineStore(id, ...) 自然创建一个独立模块,无嵌套、无命名空间问题,更扁平
代码体积(gzipped) ~17 kB ~2 kB
依赖数量 较多依赖 极少依赖(几乎无)
运行效率 优秀(底层基于 Vue 响应式) 优秀(与 Vuex 相近,底层同样基于 Vue 3 响应式)
Devtools 支持时间旅行、快照 支持时间旅行、快照,且更直观,与 Devtools 集成更紧密
热更新 支持但不完美,需要额外处理 原生支持 store 热更新,无需刷新页面
SSR 支持 需手动创建 store 实例 自动支持,每个请求独立实例,避免状态污染
迁移成本 --- 可从 Vuex 增量替换 ,两者可共存;社区有 vuex-to-pinia 辅助脚本转换 80% 代码;主要改动点:state 改为函数、mutations 合并入 actions、commit 改直接调用、mapState/mapActions 换为 storeToRefs + 解构
官方推荐 Vue 2 时代官方标准,Vue 3 可用但不再推荐 Vue 3 官方推荐 ,Vue 2 也可通过插件使用(@pinia/nuxt 或普通 Vue 2 项目)
javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
  // 返回初始状态
  state: () => ({
    count: 0
  }),
  // 计算属性(派生状态)
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  // 可以包含同步/异步操作
  actions: {
    increment() {
      this.count++
    },
    async asyncIncrement() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.count++
    },
  },
})

13、构建工具:Vue CLI vs Vite

特性 Vue CLI(基于 webpack) Vite
启动速度 慢(打包后启动) 极快(按需编译,原生 ES modules)
生产构建 基于 webpack,配置灵活但复杂 基于 Rollup,预配置更简单
插件生态 丰富的 webpack 插件 插件系统兼容 Rollup 插件,且提供 Vite 插件
配置方式 vue.config.js vite.config.js
开发环境 HMR 较慢(大规模项目) HMR 快速,保留状态
环境变量 VUE_APP_* VITE_*

15、虚拟 DOM 与 diff 算法

diff 策略

  • 同层比较:只比较同一层节点,不跨层。
  • 双端比较(Vue 2):新旧 VNode 的 children 数组通过头尾交叉比较,找到可复用的节点。
  • 静态提升(Vue 3):编译时标记静态节点,更新时跳过它们。
  • Patch flag(Vue 3):标记动态节点,只更新变化的部分

总结

虚拟 DOM 是一种用 JS 对象模拟真实 DOM 的结构,通过 diff 算法对比新旧 VNode,找出差异并批量更新真实 DOM,减少了直接操作 DOM 的性能开销。Vue 2 的 diff 采用双端比较,Vue 3 则引入了静态提升和 patch flags,进一步优化了更新效率。key 是 diff 过程中识别节点的重要依据,使用稳定的 key 可以保证节点复用,避免渲染错误。

vue 常见面试题

第一、问:"谈谈你对 Vue 渐进式框架的理解?"

  • 核心定义:首先清晰说明"渐进式"就是"按需取用,逐步增强"。

  • 层次说明:分层次阐述其含义:

    • 视图层核心:可以只用它做 DOM 渲染,像 jQuery 一样。

    • 组件系统:可以只用它来构建可复用的组件。

    • 客户端路由:需要页面跳转时,加入 Vue Router。

    • 状态管理:应用复杂、组件间通信频繁时,加入 Pinia。

    • 构建工具链:对于大型项目,使用 Vite 获得完整的工程化支持。

  • 举例说明:结合实际场景,例如"我可以在一个旧的 JSP 或 PHP 页面里,只引入 Vue 来做一个交互复杂的表单,而不用重构整个页面"。

  • 总结优势:最后总结其优点------灵活、低门槛、风险小,无论是小项目还是大应用都能很好地适应。

第二、 Vue 的响应式原理是什么?

一句话总结:Vue 通过数据劫持监听数据变化,通过依赖收集建立起数据和视图的映射关系,当数据变化时,通过异步派发更新异步地通知所有相关的视图进行diff对比并重新渲染

一个具体的例子来说明整个过程

假设你有如下代码:

javascript 复制代码
data() {
  return { name: 'Alice' }
}
// 页面上有:<div>{{ name }}</div>

1、 初始化 :Vue 对你的 name 属性进行劫持,设置 getset

2、 首次渲染 :渲染页面时,需要读取 name 的值,触发了 get。Vue 看到是"渲染Watcher"在读这个数据,就把这个 Watcher 添加到 name 的 "依赖盒子"(Dep)里。此时,name 和页面建立了联系。

3、 你修改数据 :执行 this.name = 'Bob',触发了 nameset

4、 派发更新set 通知 name 的 "依赖盒子":"我的值变了!"

5、 依赖盒子通知:"依赖盒子"里存储着"渲染Watcher",于是通知它:"你依赖的数据变了,该干活了!"

6、 异步更新:"渲染Watcher"把自己放到异步队列里,在下一个Tick,重新执行渲染函数,生成新的虚拟DOM,计算差异,最后更新页面上的文字从"Alice"变为"Bob"。

第三、 为什么 Vue 默认使用模板语法,而不是像 React 那样使用 JSX?模板语法有什么好处?"

  • 降低门槛,提升体验:核心原因是开发体验和学习曲线。模板语法基于标准 HTML,对初学者和设计师非常友好,符合直觉。

  • 声明式与直观性:模板能非常清晰、直观地表达 UI 的结构和逻辑,代码可读性强。

  • 编译时优化:这是关键的技术优势。强调 Vue 的模板不是直接在运行时解释的,而是被编译器转换成了优化的渲染函数。可以具体说出几点优化,如静态提升动态标记等,证明你理解其底层原理。

  • 关注点分离:提及单文件组件(SFC)的结构优势,模板、逻辑、样式各司其职,便于团队协作和长期维护。

  • 灵活性:最后可以补充一点,Vue 并不排斥 JSX。对于需要高度动态和编程灵活性的场景,开发者完全可以选择使用 JSX,体现了 Vue 设计的灵活性。

第四、生命周期钩子映射表​

阶段 Vue 2 钩子 Vue 3 钩子(Options API) Vue 3 钩子(Composition API)
初始化 beforeCreate, created setup() 代替 beforeCreate/created
挂载 beforeMount, mounted onBeforeMount, onMounted
更新 beforeUpdate, updated onBeforeUpdate, onUpdated
卸载 beforeDestroy, destroyed beforeUnmount, unmounted onBeforeUnmount, onUnmounted
错误捕获 errorCaptured onErrorCaptured
其他 activated, deactivated(keep-alive) onActivated, onDeactivated
调试 renderTracked, renderTriggered(开发) onRenderTracked, onRenderTriggered

第五、 "watch 和 watchEffect 有什么区别?应该如何选择?"​

  • 依赖追踪:​

    • watchEffect:自动追踪。它会自动收集其回调函数中访问到的所有响应式数据作为依赖。​

    • watch:手动指定。你必须明确地告诉它要监听哪个数据源。​

  • 执行时机:​

    • watchEffect:立即执行一次,然后等待依赖变化后再次执行。​

    • watch:默认是懒执行的,只有当被监听的数据源变化时才执行。可以通过 { immediate: true } 选项使其立即执行。​

  • 访问旧值:​

    • watchEffect:无法访问变化前的值。​

    • watch:可以同时访问新值和旧值,方便进行比较。

第六、 生命周期钩子(执行顺序、使用场景)

父子组件生命周期顺序

  • 创建:父 beforeCreate → 父 created → 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted → 父 mounted
  • 更新:父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
  • 销毁:父 beforeDestroy → 子 beforeDestroy → 子 destroyed → 父 destroyed

常用钩子作用

  • beforeCreate:实例初始化后,数据观测和事件配置之前。无法访问 data、props。
  • created:可访问数据,但 DOM 未挂载,适合异步请求、初始化数据。
  • mounted:DOM 已挂载,可操作 DOM,适合第三方库初始化。
  • beforeDestroy:销毁前,适合清除定时器、取消订阅。
  • activated / deactivatedkeep-alive 组件激活/停用。

第七、$nextTick 原理及使用场景

原理

Vue 的异步更新队列。数据变化后,Vue 将开启一个队列,把同一个事件循环内的所有数据变化缓存起来,然后在下一个事件循环(microtask)统一执行 DOM 更新。$nextTick 的回调会在 DOM 更新完成后执行。

使用场景

  • 在数据变化后,需要获取更新后的 DOM 结构。
  • 需要在 mounted 钩子中确保子组件渲染完成。
  • 异步操作后需要等待 DOM 同步。

面试回答

"$nextTick 利用 Promise 或 MutationObserver 等微任务机制,将回调延迟到下次 DOM 更新循环之后执行。我们常用来解决数据变化后立即操作 DOM 的问题,比如滚动到底部、获取元素宽高等。Vue 3 中同样有 nextTick 函数,可在组合式 API 中使用。

第八、 keep-alive 实现原理及生命周期

作用

缓存不活动的组件实例,避免反复渲染。

原理

内部维护一个缓存对象(键是组件的 key 或自身),当组件切换时,将被移除的组件实例保留在缓存中,而不是销毁。再次激活时从缓存取出复用,触发 activateddeactivated 钩子。

相关属性

  • include / exclude:正则或数组,指定要缓存/不缓存的组件。
  • max:最大缓存数,超出时根据 LRU 策略删除。

生命周期

  • 首次进入:createdmountedactivated
  • 缓存后再次进入:activated(不会重新执行 created / mounted
  • 离开时:deactivated

面试回答

"keep-alive 是一个抽象组件,它通过缓存 VNode 来保留组件状态,避免重复渲染。内部使用 LRU 算法管理缓存,可以通过 includemax 控制缓存策略。被缓存的组件会多出 activateddeactivated 钩子,用于在激活/停用时执行逻辑。

第九、 组合式 API 与选项式 API 的优缺点

选项式 API(Vue 2 主流):

  • 优点:结构清晰(data、methods、computed 分块),适合初学者。
  • 缺点:逻辑分散,复杂组件难以维护;复用逻辑需借助 mixin,存在缺陷。

组合式 API(Vue 3 引入):

  • 优点:

    • 逻辑集中,按功能组织代码,可读性和可维护性高。
    • 逻辑复用简单,通过组合函数(hooks)实现,无命名冲突。
    • 更好的 TypeScript 类型推断。
  • 缺点:学习曲线稍陡,对初学者不够直观。

面试回答

"选项式 API 将组件选项按类型划分,代码直观但逻辑分散。组合式 API 将相关逻辑聚合在 setup 中,通过组合函数实现复用,尤其适合大型复杂组件。Vue 3 并未废弃选项式 API,两者可混用,但组合式 API 提供了更好的逻辑复用能力和类型支持,是未来的推荐写法。"

第十、SSR 原理及优缺点

原理

  • 服务端运行 Vue 应用,生成 HTML 字符串直接返回给浏览器,客户端再"激活"(hydrate)为可交互应用。
  • 同构:同一份代码在服务端和客户端均可运行。

优点

  • 更好的 SEO:搜索引擎能抓取完整 HTML。
  • 更快的首屏加载:用户无需等待 JS 下载即可看到内容。

缺点

  • 开发复杂度高:需考虑 Node.js 环境兼容性。
  • 服务器负载大:每个请求都重新渲染,需注意缓存策略。
  • 部分 API 在服务端不可用(如 window、document),需条件判断。

面试回答

"SSR 在服务端将 Vue 组件渲染成 HTML,发送给客户端,然后客户端进行激活。它主要解决 SPA 的 SEO 问题和首屏加载速度。但实现成本较高,需要处理服务端和客户端环境的差异,并关注服务器性能。通常我们会借助 Nuxt.js(Vue 2)或 Nuxt 3(Vue 3)这样的框架来简化 SSR 开发。"

第十一、Vue 3 新特性及与 Vue 2 的区别

核心新特性

  • 组合式 API:更好的逻辑复用和代码组织。
  • Proxy 响应式:解决 Vue 2 的响应式局限,性能更优。
  • Teleport:将组件内容渲染到任意 DOM 位置。
  • Fragment:组件支持多个根节点。
  • Suspense:用于异步组件加载时的占位。
  • 全局 API 改造createApp 替代 new Vue,全局配置隔离。
  • 更好的 TypeScript 支持:源码用 TS 重写,类型更完善。
  • 性能提升:编译优化(静态提升、patch flag),打包体积更小。
  • Vite 官方构建工具:开发体验极大提升。

破坏性变更

  • 移除过滤器、$children$on/$once/$offv-on.native 等。
  • v-model 默认 prop 和事件变化,支持多个绑定。
  • v-ifv-for 优先级改变。

面试回答

"Vue 3 相比 Vue 2 在响应式系统、组合式 API、性能、TypeScript 支持等方面有重大改进。它引入了 Teleport、Suspense 等内置组件,并用 createApp 创建应用,避免全局污染。虽然有一些破坏性变更,但官方提供了迁移构建和工具帮助升级。Vue 3 也带来了更现代的构建工具 Vite,提升了开发体验。"

第十二、常见API使用方式 defineEmits、defineExpose、defineOptions、defineProps、

1. defineProps -- 接收父组件传递的数据

作用 :声明组件的 props(属性),代替传统的 props 选项。

基本用法

xml 复制代码
<script setup>
// 运行时声明(自动推断类型)
const props = defineProps(['title', 'count'])

// 带类型的声明(TypeScript)
const props = defineProps<{
  title: string
  count?: number   // 可选
}>()
</script>

父组件使用

ini 复制代码
<MyComponent title="Hello" :count="10" />

2. defineEmits -- 向父组件发送事件

作用:声明组件可以触发的事件。

xml 复制代码
<script setup>
// 简单声明
const emit = defineEmits(['update', 'delete'])

// 带参数验证(TypeScript)
const emit = defineEmits<{
  (e: 'update', id: number): void
  (e: 'delete', name: string): void
}>()

// 触发事件
emit('update', 123)
</script>

父组件监听

ini 复制代码
<MyComponent @update="handleUpdate" @delete="handleDelete" />

3. defineExpose -- 暴露组件内部属性/方法给父组件(通过 ref)

作用 :默认 <script setup> 下的组件是关闭的 ,父组件无法通过 ref 访问其内部成员。使用 defineExpose 明确暴露。

xml 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++

// 只暴露 increment 和 count,其他不暴露
defineExpose({
  increment,
  count
})
</script>

父组件访问

xml 复制代码
<template>
  <MyComponent ref="childRef" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
const childRef = ref()

onMounted(() => {
  childRef.value.increment()   // 调用子组件方法
  console.log(childRef.value.count) // 读取子组件数据
})
</script>

4. defineOptions -- 设置组件选项(Vue 3.3+)

作用 :在 <script setup> 中声明组件名、继承属性、自定义选项等,无需单独的 <script> 块。

xml 复制代码
<script setup>
defineOptions({
  name: 'MyCustomName',      // 组件名称
  inheritAttrs: false,       // 是否继承非 prop 属性
  // 其他选项(如 components、directives 一般不在这里,但可自定义)
})
</script>

典型场景

  • 设置组件名(方便 Vue Devtools 识别)
  • 关闭属性继承(手动控制 $attrs

第十三、wacth & watchEffect 区别

特性 watch watchEffect
依赖收集 显式指定要监听的数据源(ref、reactive 属性、getter 函数) 自动收集回调函数中使用的所有响应式数据
初始执行 默认不执行 ,数据第一次变化时才执行(可配置 immediate: true 立即执行一次,同时收集依赖
访问新旧值 回调中提供旧值和新值 只能访问新值(无法直接获取旧值)
监听多个源 支持同时监听多个数据源(数组形式) 自动收集多个依赖,无需显式指定
精准控制 可以配置 deepflushimmediate 等选项 只有 flush 选项(以及 onTrack/onTrigger 调试)
停止监听 调用返回的函数 同样返回停止函数
适用场景 需要知道具体哪个数据变化、需要旧值、需要惰性执行 简单副作用,自动跟踪依赖,不需要旧值

1、watch 基础用法

xml 复制代码
<script setup>
import { ref, reactive, watch } from 'vue'

const count = ref(0)
const state = reactive({ name: 'Vue', age: 3 })

// 监听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

// 监听 getter 函数
watch(
  () => state.age,
  (newAge, oldAge) => {
    console.log(`年龄从 ${oldAge} 变为 ${newAge}`)
  }
)

// 监听多个源(数组)
watch([count, () => state.age], ([newCount, newAge], [oldCount, oldAge]) => {
  console.log(`count: ${oldCount}->${newCount}, age: ${oldAge}->${newAge}`)
})

// 立即执行 + 深度监听
watch(
  () => state,
  (newVal, oldVal) => {
    console.log('state 变化了', newVal)
  },
  { immediate: true, deep: true }
)
</script>

2、watchEffect 基础用法

xml 复制代码
<script setup>
import { ref, reactive, watchEffect } from 'vue'

const count = ref(0)
const state = reactive({ name: 'Vue', age: 3 })

// 自动收集依赖:count 和 state.age
watchEffect(() => {
  console.log(`count: ${count.value}, age: ${state.age}`)
})
// 初始立即输出:count: 0, age: 3
// 之后任何依赖变化都会重新执行

// 停止监听
const stop = watchEffect(() => { /* ... */ })
stop() // 手动停止
</script>

第十四、provide() 和 inject() 跨层级组件通信例子

xml 复制代码
<!-- Ancestor.vue -->
<script setup>
import { provide, ref } from 'vue'

// 提供普通值
provide('theme', 'dark')

// 提供响应式数据(推荐)
const count = ref(0)
const updateCount = () => count.value++
provide('count', count)
provide('updateCount', updateCount)
</script>
xml 复制代码
<!-- Descendant.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme', 'light') // 默认值 'light'
const count = inject('count')
const updateCount = inject('updateCount')

// 使用
console.log(theme)   // 'dark'
count.value++        // 响应式更新
updateCount()        // 调用方法
</script>

第十五、toRefs、toRef、isRef、unref 响应式引用工具

1. toRefs -- 将响应式对象转换为普通对象,每个属性都是 ref

作用 :解构 reactive 对象时保持响应性。

javascript 复制代码
import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0, name: 'Vue' })

// ❌ 直接解构会丢失响应性
let { count, name } = state
count++  // 不会触发视图更新

// ✅ 使用 toRefs 包装
const stateRefs = toRefs(state)
const { count, name } = stateRefs
count.value++ // 响应式生效

原理toRefs 为每个属性创建一个 ref 链接到原对象的对应属性。

2. toRef -- 为响应式对象的单个属性 创建 ref

作用 :保持对源对象属性的响应式引用,常用于将 props 的某个属性转为 ref 以便传递。

javascript 复制代码
import { reactive, toRef } from 'vue'

const state = reactive({ count: 0 })
const countRef = toRef(state, 'count')

countRef.value++   // 同时修改 state.count
console.log(state.count) // 1

典型场景 :组合函数接收 props 中的某个属性并保持响应性。

javascript 复制代码
// useFeature.js
import { toRef, watchEffect } from 'vue'
export function useFeature(propRef) {
  const propVal = toRef(propRef)  // 确保是 ref
  watchEffect(() => {
    console.log(propVal.value)
  })
}

3. isRef -- 判断某个值是否为 ref

javascript 复制代码
import { ref, reactive, isRef } from 'vue'

const count = ref(0)
const state = reactive({})
console.log(isRef(count)) // true
console.log(isRef(state)) // false

4. unref -- 如果参数是 ref 则返回其 value,否则返回参数本身

作用 :方便地获取值,无需手动判断 .value

javascript 复制代码
import { ref, unref } from 'vue'

const count = ref(0)
const plain = 42

console.log(unref(count)) // 0
console.log(unref(plain)) // 42

// 等价于
function myUnref(val) {
  return isRef(val) ? val.value : val
}

常用场景 :在组合函数中,参数可能是 ref 也可能是普通值,使用 unref 统一处理。

第十六 跨层级通信:provide / inject

当需要从祖先组件向其所有后代组件传递数据时,使用 provide 和 inject 可以避免逐层传递 props(即"prop drilling")。

  • provide: 在祖先组件中提供数据或方法。

  • inject: 在任何后代组件中注入(接收)这些数据或方法。

祖先组件 App.vue
xml 复制代码
<script setup>
import { ref, provide } from 'vue';
import DeepChild from './DeepChild.vue';
const theme = ref('light');

function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light';
}

// 提供数据和方法给所有后代
provide('theme', theme);
provide('toggleTheme', toggleTheme);

</script>

<template>
    <div :class="theme">
        <DeepChild />
    </div>
</template>
后代组件 DeepChild.vue
xml 复制代码
<script setup>
import { inject } from 'vue';
// 注入来自祖先的数据和方法
const theme = inject('theme', 'light'); // 'light' 是默认值
const toggleTheme = inject('toggleTheme');
</script>

<template>
    <p>Current theme is: {{ theme }}</p>
    <button @click="toggleTheme">Toggle Theme</button>
</template>
相关推荐
yingyima2 小时前
深入解析:定时任务失败重试机制的底层原理与实践
前端
踩着两条虫2 小时前
VTJ.PRO v2.4.2 私有化部署与升级实操指南
前端·人工智能·低代码·架构·数据挖掘
木斯佳2 小时前
前端八股文面经大全:美团前端暑期实习一面(2026-06-08)·面经深度解析
前端
Uso_Magic2 小时前
VOL_实现APP多文件上传_前端多文件显示!
前端
问心无愧05132 小时前
ctf sow web入门112
android·前端·笔记
2301_800895102 小时前
线性代数保研面试复习
线性代数·面试·保研
库拉大叔2 小时前
工具调用效率对比实测:GPT-5.5与Gemini 3.5 Flash性能评估
java·前端·人工智能
艾伦野鸽ggg2 小时前
CSS容器查询和悬浮间隙问题
前端·css
云水一下3 小时前
Vue.js从零到精通系列(一):初识Vue——背景、环境与第一个应用
前端·javascript·vue.js