2025 vue3面试题汇总,通俗易懂
一、基础概念与核心特性
1. Vue3 相比 Vue2 的改进(通俗版)
问题:Vue3 比 Vue2 好在哪?
答案:
- 更快:
- Proxy 代理:Vue2 的响应式像“逐个监听保险箱”(每个属性单独监听),Vue3 的 Proxy 像“直接监控整个房间”(监听整个对象变化)。
- 编译优化:Vue3 在编译阶段标记哪些是动态内容(如
{{ count }}
),更新时跳过静态内容(如纯文字)。
- 更小:通过 Tree-shaking(摇树优化),只打包你用到的功能,减少代码体积。
- 更好用:
- Composition API:像搭积木一样组合逻辑(比如把“计数器逻辑”抽成函数,多个组件复用)。
- 新组件:
<Teleport>
:把组件渲染到任意位置(比如弹窗放到 body 下,避免被父组件样式影响)。<Suspense>
:优雅处理异步加载(比如数据加载时显示 Loading 动画)。
2. Composition API vs Options API(场景对比)
问题:为什么要用 Composition API?
答案:
-
Options API(Vue2 风格):
- 把代码按类型分块(data、methods、生命周期),适合简单组件。
- 缺点:逻辑分散,比如一个“搜索功能”的 data、methods 可能分布在多处。
// Options API 示例 export default { data() { return { keyword: '' } }, methods: { search() { ... } }, mounted() { this.search() } }
-
Composition API(Vue3 风格):
- 在
setup()
中,按功能组织代码(比如把搜索相关的数据、方法写在一起)。 - 优点:逻辑复用更方便(类似 React Hooks)。
// Composition API 示例 export default { setup() { const keyword = ref(''); const search = () => { ... }; onMounted(search); return { keyword, search }; } }
- 在
二、响应式原理(手绘理解)
3. Vue3 的响应式原理
问题:Vue3 如何实现数据变化自动更新视图?
答案:
-
Proxy 代理对象:
- 当你修改数据时,Proxy 会“拦截”操作(比如
obj.a = 1
),通知视图更新。 - 对比 Vue2:Vue2 使用
Object.defineProperty
,无法监听新增属性和数组下标变化(必须用this.$set
)。
- 当你修改数据时,Proxy 会“拦截”操作(比如
-
代码模拟(简化版):
function reactive(obj) { return new Proxy(obj, { get(target, key) { console.log('读取了', key); return Reflect.get(target, key); }, set(target, key, value) { console.log('更新了', key); return Reflect.set(target, key, value); } }); } const obj = reactive({ a: 1 }); obj.a = 2; // 触发 set 拦截,更新视图
4. ref
和 reactive
的区别(买菜比喻)
问题:什么时候用 ref
?什么时候用 reactive
?
答案:
-
ref
:- 用于包装 基本类型(数字、字符串等),因为 Proxy 无法直接监听基本类型。
- 使用方式:必须通过
.value
访问(就像买菜用袋子装,取菜要打开袋子)。
const count = ref(0); console.log(count.value); // 0 count.value++;
-
reactive
:- 用于包装 对象/数组,可以直接访问属性(就像直接拿菜篮子,不用拆包装)。
const user = reactive({ name: '张三' }); console.log(user.name); // 张三 user.name = '李四';
-
总结:
- 简单值用
ref
,复杂对象用reactive
。 - 如果不想写
.value
,可以用toRefs
解构对象(见下文)。
- 简单值用
toRefs
是 Vue 3 中用于处理响应式对象的重要工具函数,主要用于将 reactive
对象转换为普通对象,同时确保每个属性都保持响应性。这在解构响应式对象或将其属性传递给子组件时非常有用。
使用场景
- 解构响应式对象:直接解构
reactive
对象会失去响应性,而使用toRefs
可以避免这一问题。 - 组件间通信:通过
toRefs
将响应式数据传递给子组件,确保数据在传递过程中仍能保持响应性。
基本用法
import { reactive, toRefs } from 'vue';
const state = reactive({
foo: 1,
bar: 2,
});
const stateRefs = toRefs(state);
// stateRefs 的每个属性都是 ref 对象,修改它们的值会触发视图更新
stateRefs.foo.value++; // 视图会自动更新
示例代码
解构并保持响应性
<template>
<div>
<p>Foo: {{ foo }}</p>
<p>Bar: {{ bar }}</p>
<button @click="incrementFoo">Increment Foo</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
foo: 1,
bar: 2,
});
const { foo, bar } = toRefs(state);
function incrementFoo() {
foo.value++;
}
return {
foo,
bar,
incrementFoo,
};
},
};
</script>
在组合式 API 中使用
import { reactive, toRefs } from 'vue';
function useCounter() {
const state = reactive({
count: 0,
});
function increment() {
state.count++;
}
return {
...toRefs(state),
increment,
};
}
注意事项
- 访问方式:返回的对象属性是
ref
对象,在 JavaScript 中需通过.value
访问;模板中则无需.value
。 - 适用范围:仅适用于
reactive
对象,不支持普通对象或ref
对象。 - 性能影响:大量属性可能带来一定性能开销。
总结而言,toRefs
提供了一种便捷的方式来处理响应式对象,尤其在需要解构或传递响应式数据时,能够有效简化逻辑并保持数据的响应性。
三、进阶 API 与实战技巧
5. watch
和 watchEffect
(场景区分)
问题:监听数据变化用哪个?
答案:
-
watch
:- 明确监听某个数据,适合精确控制(比如监听搜索关键词变化,触发请求)。
watch( keyword, (newVal) => { fetchData(newVal) }, { immediate: true } // 立即执行一次 );
-
watchEffect
:- 自动追踪依赖,适合副作用操作(比如根据多个数据变化更新 DOM)。
watchEffect(() => { console.log('关键词和页码变化了:', keyword.value, page.value); fetchData(); });
6. 组件通信:Provide/Inject(跨层级传参)
问题:爷爷组件如何直接传数据给孙子组件?
答案:
- 步骤:
- 爷爷组件用
provide
提供数据。 - 孙子组件用
inject
获取数据。
- 爷爷组件用
- 代码示例:
// 爷爷组件 import { provide } from 'vue'; setup() { provide('theme', 'dark'); // 提供数据 } // 孙子组件 import { inject } from 'vue'; setup() { const theme = inject('theme', 'light'); // 第二个参数是默认值 return { theme }; }
四、性能优化(通俗策略)
7. 如何让 Vue3 应用更快?
答案:
-
代码层面:
- 使用
v-once
标记静态内容(只渲染一次)。 - 用
v-memo
缓存动态组件(比如表格行,只有 ID 变化时才重新渲染)。
<div v-for="item in list" :key="item.id" v-memo="[item.id]"> {{ item.name }} </div>
- 使用
-
打包优化:
- 按需引入组件库(比如 Element Plus 只导入用到的 Button、Input)。
- 使用异步组件(懒加载),减少首屏代码体积。
// 异步加载组件 const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'));
五、高频面试代码片段
8. 自定义指令:点击外部关闭弹窗
场景:点击弹窗外部区域关闭弹窗。
代码:
// 全局指令 v-click-outside
app.directive('click-outside', {
mounted(el, { value: callback }) {
el.handler = (e) => {
if (!el.contains(e.target)) callback();
};
document.addEventListener('click', el.handler);
},
unmounted(el) {
document.removeEventListener('click', el.handler);
}
});
// 使用
<template>
<div v-click-outside="closeModal">弹窗内容</div>
</template>
六、项目经验(回答技巧)
9. 如何回答“封装通用组件”?
示例:
- 场景:封装一个表单组件,支持校验和提交。
- 步骤:
- 通过
props
接收表单配置(如字段规则)。 - 使用
v-model
绑定每个输入项的值。 - 暴露
validate()
方法供父组件调用。 - 使用插槽(slot)允许自定义布局。
<template> <form @submit.prevent="submit"> <slot></slot> <button type="submit">提交</button> </form> </template> <script> export default { methods: { validate() { /* 校验逻辑 */ }, submit() { this.$emit('submit'); } } } </script>
- 通过
总结
以上内容通过通俗比喻、实际场景和代码示例,拆解了 Vue3 的核心知识点。建议边学边写代码实践,结合 Vue3 官方文档 查漏补缺!