一、生命周期

核心变化

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(父->子)

 1<!-- 父组件 -->
 2<Child :title="data" />
 3<!-- 子组件 -->
 4<script setup>
 5defineProps(['title'])
 6</script>
  • 类型校验defineProps({ title:{ type:String, required:true } })
  • 单项数据流:子组件不能直接修改props(需要通过emit通知父组件)。

emits(子->父)

 1<!-- 子组件 -->
 2<button @click="$emit('update', value)" ></button>
 3<!-- 父组件 -->
 4<Child @update="handleUpdate" />
  • Vue3特性defineEmits(['update'])显式申明事件。

2.v-model双向绑定(语法糖进阶)

  • 单值绑定
 1<!-- 父组件 -->
 2<Child v-model="message" />
 3<!-- 子组件 -->
 4<input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" >
 5<script setup>
 6defineProps(["modalValue"])
 7defineEmits(["update:modelValue"])
 8</script>
 9
  • 多值绑定
 1<Child v-model:title="title" v-model:content="content" />

3.ref/expose(父访问子组件)

  • 模版引用
 1<!-- 父组件 -->
 2<Child ref="childRef" />
 3<script setup>
 4const childRef = ref(null);
 5
 6childRef.value.childmethod();
 7</script>
 8
 9<!-- 子组件 -->
10const childMethod = () => {};
11defineExpose({ childMethod })
12</script>

4.provide/inject(跨层级通信)

  • 依赖注入
 1import { provide } from 'vue';
 2provide('theme', 'dark');
 3
 4
 5import { inject } from 'vue';
 6
 7const theme = inject('theme', 'light')
  • 响应式数据
 1const count = ref(0);
 2provide('count', count);

5.事件总线(Event Bus)

  • Vue3官方废弃$on,推荐第三方库(如mitt)
 1import mitt from 'mitt';
 2export const emitter = mitt();
 3
 4
 5emitter.emit('refresh', data);
 6
 7
 8emitter.on('refresh', (data) => {  })
 9
10
11onUnmounted(() => emitter.off('refresh'))

6.状态管理(pinia)

  • 替代Vuex的官方状态库
 1export const useCounterStore = defineStore('counter',{
 2   state:()=>({ count: 0 }),
 3   actions:{
 4       increment() { this.count++; }
 5   }
 6})
 7
 8
 9const store = useCounterStore();
10store.increment();

7.属性透传($attr)

  • 透传非props属性
 1<!-- 父组件 -->
 2<Child class="child-style" data-id="123" />
 3
 4<!-- 子组件 -->
 5<div v-bind="$attrs"></div>
 6<script setup>
 7
 8defineOptions({ inheritAttrs: false });
 9</script>

8.模版引用(Template Refs)

  • 直接操作DOM
 1<template>
 2    <input ref="inputRef" />
 3</template>
 4
 5<script setup>
 6const inputRef = ref(null);
 7onMounted(() => inputRef.value.focus())
 8</script>

高频面试题

1.父子组件通信方式有哪些?

  • 父->子:props、$attr、ref。
  • 子->父:emits、v-model。
  • 双向:v-model、状态管理。

2.provide/inject能否替代Vuex?

  • 适用场景:跨层级但关系明确的组件。
  • 局限性:不适合全局状态管理,无法跟踪状态变化历史。

3.Vue3为什么不移除事件总线?

  • 设计理念:避免全局事件导致代码维护困难。
  • 代替方案:使用pinia管理状态或mitt库实现事件总线。

4.如何实现兄弟组件通信?

  1. 通过共同父组件中转(props/emits)
  2. 事件总线(mitt)
  3. 状态管理(Pinia)

5.动态组件如何保持状态?

  • <KeepAlive>组件
 1<KeepAlive>
 2    <component :is="currentComponent" />
 3</KeepAlive>

实战场景选择

场景推荐方案
父子简单数据传递Props/Emits
表单双向绑定v-model语法糖
跨层级组件共享配置provide/inject
复杂全局状态管理Pinia
非父子组件松散通信事件总线(mitt)
组件模板直接操作ref/expose

三、修饰符

核心修饰符分类及作用

1.事件修饰符

  • stop:阻止事件冒泡。
 1<button @click.stop="handleClick">点击</button>
  • prevent:阻止默认行为。
 1<form @submit.prevent="onSubmit"></form>
  • capture:使用捕获模式。
 1<div @click.capture="handleCapture">捕获触发</div>
  • self:仅当事件从元素自身触发时执行。
 1<div @click.self="handleSelf">仅自身点击有效</div
  • once:事件只触发一次。
 1<button @click.once="handleOnce">只触发一次</button>
  • passive:提升滚动性能,不阻止默认行为。
 1<div @scroll.passive="onScroll">滚动优化</div>

2.v-model修饰符

  • lazy:输入框失焦后更新数据(替代input为change事件)。
 1<input v-model.lazy="message" />
  • number:将输入值转为数值类型。
 1<input v-model.number="age" type="number" />
  • trim:自动去除首位空格。
 1<input v-model.trim="username" />

3.键盘修饰符

  • 键名修饰符:直接使用按键名(如:.enter,.tab,.esc)。
 1<input @keyup.enter="submit" />
  • 系统修饰键:.ctrl,.alt,.shift,.meta(MAC的Command健)
 1<button @click.ctrl="handleCtrlClick">需按住 Ctrl 点击</button>
  • .exact:精确匹配系统修饰键组合。
 1<button @click.ctrl.exact="onlyCtrl">仅 Ctrl 按下时触发</button>

4.鼠标修饰符

  • .left,.right,.middle:限制鼠标按键。
 1<div @mousedown.right="handleRightClick">右键点击</div>

Vue3修饰符新特性

1…sync修饰符的替代

  • Vue2:通过.sync实现父子组件双向绑定。
  • Vue3:改用v-model:propName+emit(‘update:propName’)。
 1<!-- 父组件 -->
 2<Child v-model:title="pageTitle" />
 3
 4<!-- 子组件 -->
 5<script setup>
 6defineProps(['title'])
 7defineEmits(['update:title'])
 8</script>

2…native修饰符移除

  • Vue2:组件上绑定原生事件需用.native。
  • Vue3:默认不绑定到根元素,需通过emits声明或手动绑定。
 1<!-- 父组件 -->
 2<Child @click="handleClick" /> <!-- 需子组件 emit('click') -->

3.自定义组件支持v-model修饰符

  • 通过modelModifiers访问修饰符。
 1<!-- 父组件 -->
 2<CustomInput v-model.capitalize="text" />
 3
 4<!-- 子组件 -->
 5<script setup>
 6const props = defineProps(['modelValue', 'modelModifiers'])
 7const emit = defineEmits(['update:modelValue'])
 8
 9const handleInput = (e) => {
10  let value = e.target.value
11  if (props.modelModifiers.capitalize) {
12    value = value.charAt(0).toUpperCase() + value.slice(1)
13  }
14  emit('update:modelValue', value)
15}
16</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:动态绑定属性(缩写 :)。
 1<img :src="imageUrl" :alt="altText">
 2<!-- 动态属性名 -->
 3<div :[dynamicAttr]="value"></div>
  • v-model:表单双向绑定(组合式API增强)。
 1<input v-model="text">  <!-- 默认对应 `modelValue` -->
 2<!-- 自定义组件多v-model -->
 3<CustomInput v-model:title="title" v-model:content="content" />

2.条件渲染

  • v-if/v-else-if/v-else:动态添加/移除DOM元素。
 1<div v-if="score > 90">A</div>
 2<div v-else-if="score > 60">B</div>
 3<div v-else>C</div>
  • v-show:通过css切换显示(display:none)。
  • v-if与v-show的区别:v-show初始渲染成本高,频繁切换时性能更好。

3.列表渲染

  • v-for:遍历数组或对象
 1<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:监听事件(缩写 @)
 1<button @click="handleClick">点击</button>
 2<!-- 事件修饰符 -->
 3<form @submit.prevent="onSubmit"></form>

5.其他指令

  • v-html:渲染原始HTML(警惕XSS攻击)
  • v-text:替代 {{ }} 插值
  • v-pre:跳过编译,保留原始内容
  • v-cloak:隐藏未编译的模版(配合css使用)
 1[v-cloak] { display: none; }
  • v-once:仅渲染一次,后续数据变化不更新。
 1<div v-once>{{ staticContent }}</div>

Vue3指令新特性

1.v-model增强

  • 支持多个v-model
 1<UserForm v-model:name="name" v-model:age="age" />
  • 自定义修饰符:通过modelModifiers访问
 1defineProps(['modelValue', 'modelModifiers']);
 2if (props.modelModifiers.capitalize) {
 3  
 4}

2.v-bind合并行为

  • 同名属性合并策略:Vue3中后绑定的属性会覆盖前面的,而Vue2会合并。

3.v-for中的ref处理

  • ref数组不再自动创建,需手动处理:
 1<div v-for="item in list" :ref="setItemRef"></div>
 2<script setup>
 3const itemRefs = [];
 4const setItemRef = el => { if (el) itemRefs.push(el); };
 5</script>

自定义指令

1.注册方式

  • 全局注册
 1app.directive('focus', {
 2  mounted(el) { el.focus(); }
 3});
  • 局部注册(组合式API)
 1<script setup>
 2const vFocus = { mounted: (el) => el.focus() };
 3</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

实战实例

  • 自动聚焦指令
 1const vFocus = {
 2  mounted(el) { el.focus(); }
 3};
  • 权限控制指令
 1const vPermission = {
 2  mounted(el, binding) {
 3    const roles = store.getters.roles;
 4    if (!roles.includes(binding.value)) {
 5      el.parentNode?.removeChild(el);
 6    }
 7  }
 8};

高频面试题

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的区别

核心区别与使用场景

​特性​refreactive
​适用数据类型​基本类型(string/number/boolean)或对象对象或数组
​访问方式​通过 .value 访问直接访问属性
​响应式原理​内部对对象类型调用 reactive基于 Proxy 的深层代理
​模板自动解包​在模板中无需 .value直接使用属性
​解构响应性​需用 toRefs 保持响应性直接解构会丢失响应性,需用 toRefs

核心面试题

1.为什么ref需要.value?

  • 设计目的:统一处理基本类型和对象类型。

    • 基本类型无法通过Proxy代理,ref通过封装对象({ value:... })实现响应式
  • 底层实现

 1function ref(value) {
 2  return { 
 3    __v_isRef: true,
 4    get value() { track(this, 'value'); return value; },
 5    set value(newVal) { value = newVal; trigger(this, 'value'); }
 6  };
 7}

2.如何选择ref和reactive?

  • 优先ref
    • 管理基本类型数据
    • 需要明确的数据引用(如传递到函数中仍保持响应性)
  • 优先reactive
    • 管理复杂对象/数组,避免频繁使用.value
    • 需要深层嵌套的响应式数据

3.如何解构reactive对象且保持响应性?

  • 使用toRefs
 1const state = reactive({ count: 0, name: 'Vue' });
 2const { count, name } = toRefs(state); 
 3count.value++; 

4.reactive的局限性是什么?

  • 无法直接替换整个对象
 1let obj = reactive({ a: 1 });
 2obj = { a: 2 }; 
  • 解决方案:使用Object.assign或ref包裹对象
 1Object.assign(obj, { a: 2 }); 
 2const objRef = ref({ a: 1 }); 

5.ref如何处理对象类型

  • 自动调用reactive
 1const objRef = ref({ count: 0 });
 2objRef.value.count++; 

6.如何使用ref实现防抖功能?

  • 自定义customRef
 1function debouncedRef(value, delay = 200) {
 2    let timeout;
 3    return customRef((track, trigger) => ({
 4        get(){
 5            track();
 6            return value;
 7        }
 8        set(newVal){
 9            clearTimeout(timeout);
10            timeout = setTimeout(() => {
11                value = newVal;
12                tigger();
13            }, delay);
14        }
15    }))
16}
17
18const 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),但嵌套在对象中需手动解包
 1<template>
 2  {{ count }} <!-- 自动解包 -->
 3  {{ objRef.count }} <!-- 需确保 objRef 是 reactive 或解包后的 ref -->
 4</template>

4.如何监听ref或reactive的变化?

  • 使用watch或watchEffect:
 1watch(countRef, (newVal) => {  });
 2watch(() => state.count, (newVal) => {  });

5.ref和reactive的性能差异

  • 基本类型:ref 更高效(无需 Proxy 代理)。
  • 对象类型:性能差异可忽略。

六、slot插槽

核心概念

1. 默认插槽

  • 父组件:传递内容到子组件默认位置
 1<Child>默认内容</Child>
  • 子组件:通过<slot>接收内容
 1<template>
 2 <div>
 3    <slot>Fallback Content</slot> 
 4 </div>
 5</template>

2.具名插槽

  • 父组件:用v-slot:name#name指定插槽名
 1 <Child>
 2    <template #header>头部内容</template>
 3    <template #default>默认内容</template>
 4    <template #footer>底部内容</template>
 5</Child>
  • 子组件:用<slot name="header">定义具名插槽
 1<template>
 2     <slot name="header"></slot>
 3     <slot></slot> <!-- 默认插槽 -->
 4     <slot name="footer"></slot>
 5</template>

3.作用域插槽

  • 子组件:通过<slot>传递数据
 1<template>
 2  <slot :user="user" :data="data"></slot>
 3</template>
  • 父组件:用v-slot="props"接收数据
 1<Child>
 2  <template #default="slotProps">
 3    {{ slotProps.user.name }}
 4  </template>
 5</Child>
  • 解构语法
 1<template #default="{ user, data }">
 2  {{ user.age }}
 3</template>

Vue3插槽新特性

1.统一语法

  • 废弃slot属性:改用v-slot指令
  • 废弃slot-scope:统一使用v-slot#语法

2.动态插槽名

 1<template #[dynamicSlotName]>
 2  动态插槽内容
 3</template>

3.$slots API变化

  • Vue2this.$slotsthis.$scopedSlots分离
  • Vue3:统一为this.$slots,作用域插槽通过函数调用
 1this.$slots.header?.() 

高频面试题

1.作用域插槽的作用是什么?

  • 数据反向传递:允许子组件向父组件传递数据,实现内容渲染逻辑的定制

2.如何传递多个插槽?

  1. 多个<template>分别指定插槽名
  2. 通过v-bind合并插槽内容(高阶组件)
 1<Child v-slot="{ data }">
 2  <template #header>Header {{ data }}</template>
 3  <template #footer>Footer {{ data }}</template>
 4</Child>

3. 动态插槽的应用场景

  • 根据状态动态切换插槽内容,如表格组件根据数据类型渲染不同列

4. slot的name属性是否支持动态绑定?

  • 支持:<slot :name="dynamicName">,但父组件需要用动态插槽名接收

高级用法和原理

1.渲染作用域

  • 插槽内容在父组件作用域编译,只能访问父组件数据(除非通过作用域插槽传递子数据)

2.作用域插槽实现原理

  • 子组件:将数据作为函数参数传递给插槽
 1render() {
 2  return h('div', this.$slots.default({ user: this.user }))
 3}

3.插槽性能优化

  • 避免在插槽内容中使用复杂计算,必要时用v-once缓存静态内容

实战实例

 1<!-- 子组件 ScopedList.vue -->
 2<template>
 3  <ul>
 4    <li v-for="item in items" :key="item.id">
 5      <slot :item="item" :index="index">
 6        默认内容:{{ item.name }}
 7      </slot>
 8    </li>
 9  </ul>
10</template>
11
12<!-- 父组件 -->
13<ScopedList :items="list">
14  <template #default="{ item, index }">
15    {{ index + 1 }}. {{ item.name }} (ID: {{ item.id }})
16  </template>
17  
18  <template #footer> 
19    <div>Total: {{ list.length }}</div>
20  </template>
21</ScopedList>

七、watch、watchEffect、computed的区别

核心区别

特性computedwatchwatchEffect
用途派生响应式数据监听数据变化,执行副作用操作自动收集依赖,执行副作用操作
返回值只读的 Ref 对象返回停止监听的函数返回停止监听的函数
依赖收集自动收集依赖,惰性计算(缓存结果)需显式指定监听源自动收集依赖,立即执行
新旧值获取可获取旧值和新值无旧值,只跟踪最新值
执行时机依赖变化时重新计算默认在组件更新前执行(flush: 'pre'默认在组件更新前执行(类似 watch
异步处理不支持支持异步操作支持异步操作

核心使用场景

1.computed

  • 场景:基于响应式数据生成新的值(如过滤列表、计算总和)
 1const fullName = computed(() => `${firstName.value} ${lastName.value}`);

2.watch

  • 场景:监听特定数据变化,执行异步或复杂逻辑(如API请求、验证)
 1watch(userId, async (newId, oldId) => {
 2  const data = await fetchUser(newId);
 3  userData.value = data;
 4}, { immediate: true }); 

3.watchEffect

  • 场景:自动跟踪依赖变化,执行副作用操作(如日志、DOM操作)
 1watchEffect(() => {
 2  console.log(`窗口大小:${window.innerWidth}x${window.innerHeight}`);
 3  document.title = `Count: ${count.value}`;
 4});

高频面试题

1.三者核心区别是什么?

  • computed:派生数据,有缓存,惰性计算
  • watch:显式监听数据源,支持新旧值对比和异步
  • watchEffect:自动收集依赖,立即执行,无旧值

2.watch和watchEffect的依赖收集方式有何不同?

  • watch:需显式指定监听目标(如() => state.a
  • watchEffect:自动收集回调函数内使用的所有响应式依赖

3.如何停止watch或watchEffect的监听?

  • 调用它们返回的停止函数
 1const stop = watch(data, callback);
 2stop(); 

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

 1const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0));
 2
 3
 4watch(total, (newVal) => {
 5  console.log("总价变化:", newVal); 
 6});

2.watch VS watchEffect

 1watch([a, b], ([newA, newB], [oldA, oldB]) => {
 2  console.log(`a从${oldA}变为${newA}, b从${oldB}变为${newB}`);
 3});
 4
 5
 6watchEffect(() => {
 7  console.log(`a=${a.value}, b=${b.value}`); 
 8});

Vue3新增特性

1.watch支持监听多个数据源

 1watch([ref1, () => reactiveObj.prop], ([val1, val2]) => {  });

2.watchPostEffect和watchSyncEffect

  • watchPostEffect:等同于watchEffect(..., { flush: 'post' }),在DOM更新后执行。
  • watchSyncEffect:等同于watchEffect(..., { flush: 'sync' }),同步执行

总结

  • computed:用于派生数据,优先使用
  • watch:用于监听特定变化数据,需精确控制依赖
  • watchEffect:用于自动依赖跟踪,简化副作用管理
个人笔记记录 2021 ~ 2025