以下为Vue3面试考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。
一、生命周期
核心变化
1.组合式API的引入
- setup()替代beforeCreate和created。所有组合式API逻辑在此函数中初始化,替代Vue2的data、method等选项式配置
- 钩子函数前缀on。生命周期函数需要从Vue显示导入,如onMounted,且只能在setup()或
<script setup>
中使用。
2.钩子函数重命名
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
3.新增钩子
- onServerPrefetch。服务端渲染(SSR)期间异步获取数据。
- 调试钩子。onRenderTracked(跟踪响应式依赖)、OnRenderTriggered(响应式变更触发)。
生命周期阶段详解
1.初始化阶段
- setup()。替代beforeCreated和created,初始化响应式数据、方法等。注意:无法访问this。
- 选项式API兼容。替代beforeCreated和created仍可用,但避免与setup()混用。
2.挂载阶段
- onBeforeMount。组件挂载到DOM前调用,此时虚拟DOM已生成但未渲染。
- onMounted。组件挂载完成,可操作DOM或发起网络请求。
3.更新阶段
- onBeforeUpdate。数据变化导致DOM更新前触发,适合获取更新前的DOM状态。
- onUpdated。DOM更新执行后,避免在此修改状态,可能导致无限循环。
4.卸载阶段
- onBeforeUnmount。组件卸载前调用,清理定时器,取消网络请求,移除事件监听。
- onUnMounted。组件卸载后触发,此时子组件已全部卸载。
5.其他钩子
- onErrorCaptured。捕获子孙组件错误,可返回false阻止冒泡。
- onActivated/onDeactivated。
<KeepAlive>
缓存组件激活/停用时调用。
高频面试题
1.父子组件生命周期执行顺序
- 挂载阶段:
父onBeforeMount->子onBeforeMount->子onMounted->父onMounted
- 更新阶段:
父onBeforeUpdate->子onBeforeUpdate->子onUpdated->父onUpdated
- 卸载阶段:
父onBeforeUnmount->子onBeforeUnmount->子onUnmounted->父onUnmounted
2.在setup()中如何访问this?
- setup()中没有this,响应式数据通过ref/reactive定义,方法直接申明。
3.异步请求放在哪个钩子?
- 客户端渲染(CSR):onMounted(确保DOM可用)。
- 服务端渲染(SSR):onServerPrefetch。
二、组件通信
1.Props/Emits(父子通信)
props(父->子)
<!-- 父组件 -->
<Child :title="data" />
<!-- 子组件 -->
<script setup>
defineProps(['title'])
</script>
- 类型校验:
defineProps({ title:{ type:String, required:true } })
- 单项数据流:子组件不能直接修改props(需要通过emit通知父组件)。
emits(子->父)
<!-- 子组件 -->
<button @click="$emit('update', value)" ></button>
<!-- 父组件 -->
<Child @update="handleUpdate" />
- Vue3特性:
defineEmits(['update'])
显式申明事件。
2.v-model双向绑定(语法糖进阶)
- 单值绑定
<!-- 父组件 -->
<Child v-model="message" />
<!-- 子组件 -->
<input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" >
<script setup>
defineProps(["modalValue"])
defineEmits(["update:modelValue"])
</script>
- 多值绑定
<Child v-model:title="title" v-model:content="content" />
3.ref/expose(父访问子组件)
- 模版引用
<!-- 父组件 -->
<Child ref="childRef" />
<script setup>
const childRef = ref(null);
// 访问子组件暴露的属性/方法
childRef.value.childmethod();
</script>
<!-- 子组件 -->
const childMethod = () => {};
defineExpose({ childMethod })
</script>
4.provide/inject(跨层级通信)
- 依赖注入
// 祖先组件
import { provide } from 'vue';
provide('theme', 'dark');
// 后代组件
import { inject } from 'vue';
// 第二个参数为默认值
const theme = inject('theme', 'light')
- 响应式数据
// 提供响应式数据
const count = ref(0);
provide('count', count);
5.事件总线(Event Bus)
- Vue3官方废弃
$on
,推荐第三方库(如mitt)
// eventBus.js
import mitt from 'mitt';
export const emitter = mitt();
// 组件A(发布事件)
emitter.emit('refresh', data);
// 组件B(订阅事件)
emitter.on('refresh', (data) => { /* ... */ })
// 组件卸载时取消订阅
onUnmounted(() => emitter.off('refresh'))
6.状态管理(pinia)
- 替代Vuex的官方状态库
// store/counter.js
export const useCounterStore = defineStore('counter',{
state:()=>({ count: 0 }),
actions:{
increment() { this.count++; }
}
})
// 组件中使用
const store = useCounterStore();
store.increment();
7.属性透传($attr)
- 透传非props属性
<!-- 父组件 -->
<Child class="child-style" data-id="123" />
<!-- 子组件 -->
<div v-bind="$attrs"></div>
<script setup>
// 禁用自动继承
defineOptions({ inheritAttrs: false });
</script>
8.模版引用(Template Refs)
- 直接操作DOM
<template>
<input ref="inputRef" />
</template>
<script setup>
const inputRef = ref(null);
onMounted(() => inputRef.value.focus())
</script>
高频面试题
1.父子组件通信方式有哪些?
- 父->子:props、$attr、ref。
- 子->父:emits、v-model。
- 双向:v-model、状态管理。
2.provide/inject能否替代Vuex?
- 适用场景:跨层级但关系明确的组件。
- 局限性:不适合全局状态管理,无法跟踪状态变化历史。
3.Vue3为什么不移除事件总线?
- 设计理念:避免全局事件导致代码维护困难。
- 代替方案:使用pinia管理状态或mitt库实现事件总线。
4.如何实现兄弟组件通信?
- 通过共同父组件中转(props/emits)
- 事件总线(mitt)
- 状态管理(Pinia)
5.动态组件如何保持状态?
<KeepAlive>
组件
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
实战场景选择
场景 | 推荐方案 |
---|---|
父子简单数据传递 | Props/Emits |
表单双向绑定 | v-model语法糖 |
跨层级组件共享配置 | provide/inject |
复杂全局状态管理 | Pinia |
非父子组件松散通信 | 事件总线(mitt) |
组件模板直接操作 | ref/expose |
三、修饰符
核心修饰符分类及作用
1.事件修饰符
- stop:阻止事件冒泡。
<button @click.stop="handleClick">点击</button>
- prevent:阻止默认行为。
<form @submit.prevent="onSubmit"></form>
- capture:使用捕获模式。
<div @click.capture="handleCapture">捕获触发</div>
- self:仅当事件从元素自身触发时执行。
<div @click.self="handleSelf">仅自身点击有效</div
- once:事件只触发一次。
<button @click.once="handleOnce">只触发一次</button>
- passive:提升滚动性能,不阻止默认行为。
<div @scroll.passive="onScroll">滚动优化</div>
2.v-model修饰符
- lazy:输入框失焦后更新数据(替代input为change事件)。
<input v-model.lazy="message" />
- number:将输入值转为数值类型。
<input v-model.number="age" type="number" />
- trim:自动去除首位空格。
<input v-model.trim="username" />
3.键盘修饰符
- 键名修饰符:直接使用按键名(如:.enter,.tab,.esc)。
<input @keyup.enter="submit" />
- 系统修饰键:.ctrl,.alt,.shift,.meta(MAC的Command健)
<button @click.ctrl="handleCtrlClick">需按住 Ctrl 点击</button>
- .exact:精确匹配系统修饰键组合。
<button @click.ctrl.exact="onlyCtrl">仅 Ctrl 按下时触发</button>
4.鼠标修饰符
- .left,.right,.middle:限制鼠标按键。
<div @mousedown.right="handleRightClick">右键点击</div>
Vue3修饰符新特性
1..sync修饰符的替代
- Vue2:通过.sync实现父子组件双向绑定。
- Vue3:改用v-model:propName+emit('update:propName')。
<!-- 父组件 -->
<Child v-model:title="pageTitle" />
<!-- 子组件 -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
2..native修饰符移除
- Vue2:组件上绑定原生事件需用.native。
- Vue3:默认不绑定到根元素,需通过emits声明或手动绑定。
<!-- 父组件 -->
<Child @click="handleClick" /> <!-- 需子组件 emit('click') -->
3.自定义组件支持v-model修饰符
- 通过modelModifiers访问修饰符。
<!-- 父组件 -->
<CustomInput v-model.capitalize="text" />
<!-- 子组件 -->
<script setup>
const props = defineProps(['modelValue', 'modelModifiers'])
const emit = defineEmits(['update:modelValue'])
const handleInput = (e) => {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
高频面试题
1.v-model的.lazy和.sync有何区别?
- .lazy:延迟数据同步(input->change事件)。
- .sync:Vue2中用于父子组件双向绑定,Vue3改用v-model:prop。
2.如何阻止事件冒泡和默认行为?
- 链式调用:
@click.stop.prevent
(顺序不影响效果)。
3.Vue3如何监听组件原生事件?
- 子组件手动绑定并emit事件
- 使用v-on="$attrs"将事件绑定到内部元素。
4.如何实现自定义v-model修饰符?
- 通过modelModifiers判断修饰符存在,并调整数据逻辑。
5..exact修饰符的作用是什么?
- 精确控制系统修饰键组合,避免其他修饰键按下触发。
四、指令
核心内置指令
1.数据绑定
- v-bind:动态绑定属性(缩写 :)。
<img :src="imageUrl" :alt="altText">
<!-- 动态属性名 -->
<div :[dynamicAttr]="value"></div>
- v-model:表单双向绑定(组合式API增强)。
<input v-model="text"> <!-- 默认对应 `modelValue` -->
<!-- 自定义组件多v-model -->
<CustomInput v-model:title="title" v-model:content="content" />
2.条件渲染
- v-if/v-else-if/v-else:动态添加/移除DOM元素。
<div v-if="score > 90">A</div>
<div v-else-if="score > 60">B</div>
<div v-else>C</div>
- v-show:通过css切换显示(display:none)。
- v-if与v-show的区别:v-show初始渲染成本高,频繁切换时性能更好。
3.列表渲染
- v-for:遍历数组或对象
<li v-for="(item, index) in items" :key="item.id">{{ index }}: {{ item.name }}</li>
- 必须指定key:优化虚拟DOM复用,避免渲染错误。
- 避免与v-if共用:优先级问题(Vue2中v-for优先,Vue3中v-if优先),应改用计算属性过滤数据。
4.事件绑定
- v-on:监听事件(缩写 @)
<button @click="handleClick">点击</button>
<!-- 事件修饰符 -->
<form @submit.prevent="onSubmit"></form>
5.其他指令
- v-html:渲染原始HTML(警惕
XSS
攻击) - v-text:替代
{{ }}
插值 - v-pre:跳过编译,保留原始内容
- v-cloak:隐藏未编译的模版(配合css使用)
[v-cloak] { display: none; }
- v-once:仅渲染一次,后续数据变化不更新。
<div v-once>{{ staticContent }}</div>
Vue3指令新特性
1.v-model增强
- 支持多个v-model
<UserForm v-model:name="name" v-model:age="age" />
- 自定义修饰符:通过
modelModifiers
访问
defineProps(['modelValue', 'modelModifiers']);
if (props.modelModifiers.capitalize) {
// 处理修饰符逻辑
}
2.v-bind合并行为
- 同名属性合并策略:Vue3中后绑定的属性会覆盖前面的,而Vue2会合并。
3.v-for中的ref处理
- ref数组不再自动创建,需手动处理:
<div v-for="item in list" :ref="setItemRef"></div>
<script setup>
const itemRefs = [];
const setItemRef = el => { if (el) itemRefs.push(el); };
</script>
自定义指令
1.注册方式
- 全局注册
app.directive('focus', {
mounted(el) { el.focus(); }
});
- 局部注册(组合式API)
<script setup>
const vFocus = { mounted: (el) => el.focus() };
</script>
2.指令生命周期钩子
- created:元素属性初始化前
- beforeMount:元素插入DOM前(Vue2的bind)
- mounted:元素插入DMO后(Vue2的inserted)
- beforeUpdate:组件更新前
- update:组件更新后
- beforeUnmount:组件卸载前(Vue2的unbind)
- unMounted:组件卸载后
3.指令参数
- el:绑定的DOM元素
- binding:绑定以下属性:
- value:指令的绑定值(如
v-dir="value"
) - oldValue:旧值(仅在
beforeUpdate和update可用
) - arg:指令参数(如
v-dir:arg
) - modifiers:修饰符对象(如
v-dir.modif->{ modif: true }
) - instance:组件实例(替代Vue2的
vnode.context
)
- value:指令的绑定值(如
实战实例
- 自动聚焦指令
const vFocus = {
mounted(el) { el.focus(); }
};
- 权限控制指令
const vPermission = {
mounted(el, binding) {
const roles = store.getters.roles;
if (!roles.includes(binding.value)) {
el.parentNode?.removeChild(el);
}
}
};
高频面试题
1.v-if和v-show的区别
- v-if:条件为假时销毁DOM,适合不频繁切换场景
- v-show:始终保留DOM,通过css切换显示,适合频繁切换
2.为什么v-for需要key?
- 虚拟DOM优化:key帮助Vue识别节点身份,避免错误使用复用元素,提升更新效率。
3.如何实现自定义指令?
- 定义指令对象并注册,通过生命周期钩子操作DOM(如自动聚焦、权限控制)。
4.v-model在自定义组件中的实现原理?
- 父组件:
v-model:propName="value"
- 子组件:接收propName,通过
emit('update:propName',value)
更新
5.Vue3中v-bind的合并策略变化
- Vue3中后绑定的属性会覆盖前面的同名属性,而Vue2会合并(如class和style)
五、ref和reactive的区别
核心区别与使用场景
特性 | ref | reactive |
---|---|---|
适用数据类型 | 基本类型(string /number /boolean )或对象 | 对象或数组 |
访问方式 | 通过 .value 访问 | 直接访问属性 |
响应式原理 | 内部对对象类型调用 reactive | 基于 Proxy 的深层代理 |
模板自动解包 | 在模板中无需 .value | 直接使用属性 |
解构响应性 | 需用 toRefs 保持响应性 | 直接解构会丢失响应性,需用 toRefs |
核心面试题
1.为什么ref需要.value?
-
设计目的:统一处理基本类型和对象类型。
- 基本类型无法通过Proxy代理,ref通过封装对象(
{ value:... }
)实现响应式
- 基本类型无法通过Proxy代理,ref通过封装对象(
-
底层实现:
function ref(value) {
return {
__v_isRef: true,
get value() { track(this, 'value'); return value; },
set value(newVal) { value = newVal; trigger(this, 'value'); }
};
}
2.如何选择ref和reactive?
- 优先ref
- 管理基本类型数据
- 需要明确的数据引用(如传递到函数中仍保持响应性)
- 优先reactive
- 管理复杂对象/数组,避免频繁使用.value
- 需要深层嵌套的响应式数据
3.如何解构reactive对象且保持响应性?
- 使用toRefs
const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 保持响应性
count.value++; // 生效
4.reactive的局限性是什么?
- 无法直接替换整个对象
let obj = reactive({ a: 1 });
obj = { a: 2 }; // 响应式丢失!
- 解决方案:使用Object.assign或ref包裹对象
Object.assign(obj, { a: 2 }); // 保持响应性
const objRef = ref({ a: 1 }); // 替换整个对象时响应式有效
5.ref如何处理对象类型
- 自动调用reactive
const objRef = ref({ count: 0 });
objRef.value.count++; // 响应式生效
6.如何使用ref实现防抖功能?
- 自定义customRef
function debouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => ({
get(){
track();
return value;
}
set(newVal){
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newVal;
tigger();
}, delay);
}
}))
}
// 使用
const text = debouncedRef('', 500);
原理深入
ref的响应式实现
- 基本类型:通过
RefImpl
类实现,而RefImpl
类内部使用了 JavaScript 原生的 getter/setter 语法(不是直接调用Object.defineProperty
)。 - 对象类型:内部转化为reactive代理
reactive的响应式实现
- 基于Proxy代理整个对象,递归处理嵌套熟悉
- 依赖收集:在get时调用track收集依赖
- 触发更新:在set时调用trigger通知更新
高频面试题
1.ref和reactive底层实现差异
- ref封装value属性,reactive使用Proxy代理整个对象
2.为什么解构reactive对象会失去响应性?
- 解构得到的是普通值,非响应式引用;使用toRefs转化为ref
3.如何在模版中正确使用ref?
- 直接使用变量名(自动解包.value),但嵌套在对象中需手动解包
<template>
{{ count }} <!-- 自动解包 -->
{{ objRef.count }} <!-- 需确保 objRef 是 reactive 或解包后的 ref -->
</template>
4.如何监听ref或reactive的变化?
- 使用watch或watchEffect:
watch(countRef, (newVal) => { /* ... */ });
watch(() => state.count, (newVal) => { /* ... */ });
5.ref和reactive的性能差异
- 基本类型:
ref
更高效(无需Proxy
代理)。 - 对象类型:性能差异可忽略。
六、slot插槽
核心概念
1. 默认插槽
- 父组件:传递内容到子组件默认位置
<Child>默认内容</Child>
- 子组件:通过
<slot>
接收内容
<template>
<div>
<slot>Fallback Content</slot> <!-- 后备内容 -->
</div>
</template>
2.具名插槽
- 父组件:用
v-slot:name
或#name
指定插槽名
<Child>
<template #header>头部内容</template>
<template #default>默认内容</template>
<template #footer>底部内容</template>
</Child>
- 子组件:用
<slot name="header">
定义具名插槽
<template>
<slot name="header"></slot>
<slot></slot> <!-- 默认插槽 -->
<slot name="footer"></slot>
</template>
3.作用域插槽
- 子组件:通过
<slot>
传递数据
<template>
<slot :user="user" :data="data"></slot>
</template>
- 父组件:用
v-slot="props"
接收数据
<Child>
<template #default="slotProps">
{{ slotProps.user.name }}
</template>
</Child>
- 解构语法
<template #default="{ user, data }">
{{ user.age }}
</template>
Vue3插槽新特性
1.统一语法
- 废弃
slot
属性:改用v-slot
指令 - 废弃
slot-scope
:统一使用v-slot
或#
语法
2.动态插槽名
<template #[dynamicSlotName]>
动态插槽内容
</template>
3.$slots API
变化
- Vue2:
this.$slots
和this.$scopedSlots
分离 - Vue3:统一为
this.$slots
,作用域插槽通过函数调用
// 子组件中访问
this.$slots.header?.() // 返回VNode数组
高频面试题
1.作用域插槽的作用是什么?
- 数据反向传递:允许子组件向父组件传递数据,实现内容渲染逻辑的定制
2.如何传递多个插槽?
- 多个
<template>
分别指定插槽名 - 通过
v-bind
合并插槽内容(高阶组件)
<Child v-slot="{ data }">
<template #header>Header {{ data }}</template>
<template #footer>Footer {{ data }}</template>
</Child>
3. 动态插槽的应用场景
- 根据状态动态切换插槽内容,如表格组件根据数据类型渲染不同列
4. slot的name属性是否支持动态绑定?
- 支持:
<slot :name="dynamicName">
,但父组件需要用动态插槽名接收
高级用法和原理
1.渲染作用域
- 插槽内容在父组件作用域编译,只能访问父组件数据(除非通过作用域插槽传递子数据)
2.作用域插槽实现原理
- 子组件:将数据作为函数参数传递给插槽
// 子组件编译后
render() {
return h('div', this.$slots.default({ user: this.user }))
}
3.插槽性能优化
- 避免在插槽内容中使用复杂计算,必要时用v-once缓存静态内容
实战实例
<!-- 子组件 ScopedList.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index">
默认内容:{{ item.name }}
</slot>
</li>
</ul>
</template>
<!-- 父组件 -->
<ScopedList :items="list">
<template #default="{ item, index }">
{{ index + 1 }}. {{ item.name }} (ID: {{ item.id }})
</template>
<template #footer> <!-- 具名插槽 -->
<div>Total: {{ list.length }}</div>
</template>
</ScopedList>
七、watch、watchEffect、computed的区别
核心区别
特性 | computed | watch | watchEffect |
---|---|---|---|
用途 | 派生响应式数据 | 监听数据变化,执行副作用操作 | 自动收集依赖,执行副作用操作 |
返回值 | 只读的 Ref 对象 | 返回停止监听的函数 | 返回停止监听的函数 |
依赖收集 | 自动收集依赖,惰性计算(缓存结果) | 需显式指定监听源 | 自动收集依赖,立即执行 |
新旧值获取 | 无 | 可获取旧值和新值 | 无旧值,只跟踪最新值 |
执行时机 | 依赖变化时重新计算 | 默认在组件更新前执行(flush: 'pre' ) | 默认在组件更新前执行(类似 watch ) |
异步处理 | 不支持 | 支持异步操作 | 支持异步操作 |
核心使用场景
1.computed
- 场景:基于响应式数据生成新的值(如过滤列表、计算总和)
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
2.watch
- 场景:监听特定数据变化,执行异步或复杂逻辑(如API请求、验证)
watch(userId, async (newId, oldId) => {
const data = await fetchUser(newId);
userData.value = data;
}, { immediate: true }); // 立即执行一次
3.watchEffect
- 场景:自动跟踪依赖变化,执行副作用操作(如日志、DOM操作)
watchEffect(() => {
console.log(`窗口大小:${window.innerWidth}x${window.innerHeight}`);
document.title = `Count: ${count.value}`;
});
高频面试题
1.三者核心区别是什么?
- computed:派生数据,有缓存,惰性计算
- watch:显式监听数据源,支持新旧值对比和异步
- watchEffect:自动收集依赖,立即执行,无旧值
2.watch和watchEffect的依赖收集方式有何不同?
- watch:需显式指定监听目标(如
() => state.a
) - watchEffect:自动收集回调函数内使用的所有响应式依赖
3.如何停止watch或watchEffect的监听?
- 调用它们返回的停止函数
const stop = watch(data, callback);
stop(); // 停止监听
4.computed和普通函数的区别
- computed会缓存结果,依赖不变时不重新计算;普通函数每次调用时都会执行
5.watch的immediate和deep选项的作用
immediate:true
:立即执行回调(初始值触发)deep:true
:深度监听对象/数组内部变化
6.什么情况下使用watchEffect替代watch?
- 当依赖项不明确或需要自动跟踪多个依赖时(如同时监听多个状态)
底层原理与性能优化
1.computed缓存机制
- 内部通过
dirty
标志位标记是否需要重新计算,依赖未变化时直接返回缓存值
2.watch的异步调度
- 默认在组件更新前执行(
flush:'pre'
),可配置'post'
(组件更新后)或'sync'
(同步执行)
3.watchEffect的依赖收集
- 在首次执行回调时收集所有响应式依赖(类似Vue2的watcher依赖收集)
4.性能注意事项
- 避免过度使用watchEffect:自动依赖收集可能导致不必要的重复执行
- 合理使用computed缓存:减少重复计算开销
- 及时清理副作用:在
onUnmounted
中停止监听,避免内存泄漏。
代码实例对比
1.computed VS watch
// 计算总价(推荐用 computed)
const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0));
// 监听总价变化(watch 不适用于此类场景)
watch(total, (newVal) => {
console.log("总价变化:", newVal); // 此时 computed 更高效
});
2.watch VS watchEffect
// watch:显式监听多个依赖
watch([a, b], ([newA, newB], [oldA, oldB]) => {
console.log(`a从${oldA}变为${newA}, b从${oldB}变为${newB}`);
});
// watchEffect:自动收集依赖
watchEffect(() => {
console.log(`a=${a.value}, b=${b.value}`); // 自动跟踪 a 和 b
});
Vue3新增特性
1.watch支持监听多个数据源
watch([ref1, () => reactiveObj.prop], ([val1, val2]) => { /* ... */ });
2.watchPostEffect和watchSyncEffect
- watchPostEffect:等同于
watchEffect(..., { flush: 'post' })
,在DOM更新后执行。 - watchSyncEffect:等同于
watchEffect(..., { flush: 'sync' })
,同步执行
总结
- computed:用于派生数据,优先使用
- watch:用于监听特定变化数据,需精确控制依赖
- watchEffect:用于自动依赖跟踪,简化副作用管理
以上是Vue3面试题的基础篇内容,如有错误欢迎评论区指正,后续还会更新Vue3进阶篇。