组合式 API是vue3 的核心特性,替代 Vue2 的选项式 API,强调逻辑复用和代码组织。基本语法如下:
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';// 1. 响应式数据
const count = ref(0); // 基本类型用 ref
const user = reactive({ // 对象类型用 reactivename: 'Alice',age: 25
});// 2. 计算属性
const isAdult = computed(() => user.age >= 18);// 3. 方法
const increment = () => {count.value++;
};// 4. 生命周期钩子
onMounted(() => {console.log('组件已挂载');
});
</script><template><div><p>{{ count }}</p><button @click="increment">+1</button><p>{{ user.name }} 是否成年:{{ isAdult ? '是' : '否' }}</p></div>
</template>
一、响应式系统
1、ref
和 reactive
(1) 数据类型
ref
-
适用于基本类型(如
number
、string
、boolean
)。 -
也可以用于对象或数组(内部会调用
reactive
处理)。 -
通过
.value
访问数据。
reactive
-
仅适用于对象或数组。
-
直接访问属性,无需
.value
。
(2) 使用场景
-
ref
-
需要处理基本类型。
-
需要将数据传递到其他函数或组件时(因
ref
是对象,可以保持引用)。
-
-
reactive
-
需要处理复杂对象或数组。
-
需要直接操作嵌套属性(如
state.user.name
)。
-
2、 toRef
和 toRefs
-
toRef
-
功能:将响应式对象(
reactive
生成)的某个属性转换为一个ref
,并保持响应式连接。 -
特点:
-
生成的
ref
与原对象的属性同步更新(修改ref
会影响原对象,反之亦然)。 -
如果原对象属性是非响应式的,
toRef
不会使其变为响应式。
-
-
import { reactive, toRef } from 'vue';const state = reactive({ count: 0 });
const countRef = toRef(state, 'count');// 修改 ref 会更新原对象
countRef.value++;
console.log(state.count); // 1// 修改原对象也会更新 ref
state.count++;
console.log(countRef.value); // 2
toRef
的典型场景
①解构响应式对象时保持响应性
直接解构 reactive
对象会丢失响应性,但用 toRef
可以解决:
const state = reactive({ count: 0 });// ❌ 错误:解构会丢失响应性
const { count } = state;// ✅ 正确:使用 toRef 保持响应性
const countRef = toRef(state, 'count');
②将 props
的某个属性转为 ref
在组合式函数中,直接解构 props
会失去响应性,此时可以用 toRef
:
<script setup>
const props = defineProps(['userId']);
const userIdRef = toRef(props, 'userId'); // 保持响应性
</script>
toRefs
-
toRef
:仅转换对象的一个属性为ref
。 -
toRefs
:将整个响应式对象的所有属性转换为普通对象,每个属性都是ref
。
const state = reactive({ count: 0, name: 'A' });
const stateRefs = toRefs(state);
// { count: Ref<0>, name: Ref<'A'> }
console.log(stateRefs.count.value); // 0
3、 shallowRef
shallowRef只会在对象的第一层进行响应式处理,不会递归地对对象内部的属性进行响应式处理。
import { shallowRef } from 'vue';const user = shallowRef({ name: 'John', address: { city: 'New York' } });
console.log(user.value); // 输出: { name: 'John', address: { city: 'New York' } }
user.value.address.city = 'San Francisco'; // 修改深层属性
console.log(user.value); // 输出: { name: 'John', address: { city: 'San Francisco' } }
// 注意,修改深层属性不会触发视图更新
4、computed
computed 用于创建计算属性,它基于一个或多个响应式数据(如 ref 或 reactive)计算出一个新的值。计算属性是惰性的,只有在被访问时才会重新计算,并且会缓存结果以提高性能。
特点
- 依赖追踪:自动追踪依赖的响应式数据,仅在依赖变化时重新计算。
- 缓存机制:计算结果会被缓存,直到依赖数据变化才重新计算。
- 返回值:必须返回一个值,通常用于派生数据。
使用场景
- 需要根据响应式数据计算派生值。
- 在模板中多次使用某个计算结果,利用缓存提升性能。
- 将复杂逻辑封装成一个属性。
<script setup>
import { ref, computed } from 'vue';const firstName = ref('John');
const lastName = ref('Doe');const fullName = computed(() => `${firstName.value} ${lastName.value}`);
</script><template><p>{{ fullName }}</p> <!-- 输出 'John Doe',依赖变化时自动更新 -->
</template>
5、watch
watch 用于监听指定的响应式数据(如 ref、reactive 或计算属性)的变化,并在数据变化时执行回调函数。
特点
- 手动指定依赖:需要明确指定监听的数据源。
- 支持深度监听:通过 { deep: true } 选项可监听对象或数组的深层变化。
- 新旧值:回调函数可接收新值和旧值,便于比较。
- 多数据监听:可通过数组或对象同时监听多个数据源。
使用场景
- 数据变化时需要执行副作用,如发送请求、更新 DOM。
- 需要监听深层对象或数组变化。
- 需要执行异步操作或依赖新旧值比较。
6、watchEffect
watchEffect 是一个简化的监听工具,它会自动追踪回调函数中使用的响应式依赖,并在依赖变化时重新运行回调,无需手动指定监听目标。
特点
- 自动依赖追踪:Vue 自动检测回调中使用的响应式数据。
- 立即执行:创建时立即运行一次以收集依赖。
- 不支持新旧值:回调函数不接收新旧值参数。
- 副作用优先:适合执行不需要返回值的操作。
使用场景
- 多个响应式数据变化时执行相同副作用。
- 依赖关系动态变化,不想手动管理。
- 简单的副作用逻辑,如日志记录或 DOM 操作。
二、Class 与 Style 绑定
1. Class 绑定
Class 绑定让你可以动态切换 CSS 类。Vue 3 提供了多种语法来实现这一点:
2.1.1. 对象语法
可以用一个对象来绑定 Class,对象的键是 CSS 类名,值是布尔值,表示是否应用该类。
<template><div :class="{ active: isActive, 'text-danger': hasError }"></div>
</template><script setup>
import { ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);
</script>
解释:
- :class 是 v-bind:class 的缩写,用于动态绑定。
- 如果 isActive 为 true,<div> 会有 active 类。
- 如果 hasError 为 true,<div> 会有 text-danger 类。
2.1.2. 数组语法
也可以用数组列出要应用的类名
<template><div :class="[activeClass, errorClass, 'static-class']"></div>
</template><script setup>
import { ref } from 'vue';const activeClass = ref('active'); // 动态类名
const errorClass = ref(''); // 空类名不渲染
</script>
渲染结果:
<div class="active static-class"></div>
如果你也想在数组中有条件地渲染某个 class,你可以使用三元表达式:
<div :class="[isActive ? activeClass : '', errorClass]"></div>
解释:
errorClass
会一直存在,但activeClass
只会在isActive
为真时才存在。
2.1.3. 混合对象和数组
<template><div :class="[isActive ? 'active' : '', { 'text-danger': hasError }]"></div>
</template><script setup>
import { ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);
</script>
解释:
- isActive ? 'active' : '':如果 isActive 为 true,应用 active 类,否则应用空字符串。
- { 'text-danger': hasError }:如果 hasError 为 true,应用 text-danger 类。
2.1.4. 绑定组件根元素的 Class
在自定义组件上绑定 Class 时,类会应用到组件的根元素。
子组件 Child.vue
:
<template><div class="child-root"><!-- 父组件传递的 class 会合并到根元素 --></div>
</template>
父组件使用:
<template><Child class="parent-class" />
</template>
渲染结果:
<div class="child-root parent-class"></div>
2.style 绑定
Style 绑定用于动态设置内联样式,同样支持对象和数组语法。
2.2.1. 对象语法
用对象绑定样式,键是 CSS 属性名,值是属性值。
<template><div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template><script setup>
import { ref } from 'vue';const activeColor = ref('red');
const fontSize = ref(16);
</script>
解释:
- :style 是 v-bind:style 的缩写。
- color 被设置为 red。
- fontSize 被设置为 16px(注意单位需要手动拼接)。
- 属性名可以用驼峰式(如 fontSize)或短横线分隔(如 font-size),推荐驼峰式。
2.2.2. 数组语法
用数组应用多个样式对象。
<template><div :style="[baseStyles, overridingStyles]"></div>
</template><script setup>
import { reactive } from 'vue';const baseStyles = reactive({color: 'blue',fontSize: '14px'
});const overridingStyles = reactive({fontSize: '18px'
});
</script>
解释:
- baseStyles 设置 color: blue 和 fontSize: 14px。
- overridingStyles 设置 fontSize: 18px,会覆盖前面的 fontSize。
- 最终结果:color: blue; font-size: 18px。
2.2.3. 自动前缀
Vue 3 会自动为某些样式属性添加浏览器前缀。例如:
<template><div :style="{ transform: 'rotate(45deg)' }"></div>
</template>
Vue 会自动生成 -webkit-transform 等前缀,确保兼容性。
2.2.4. 多值绑定(Vue 3.2+)
可以为单个属性提供多个值,Vue 会应用最后一个支持的值。
<template><div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
</template>
3.使用计算属性
对于复杂逻辑,可以用 computed 生成 Class 或 Style。
2.3.1. Class 绑定示例
<template><div :class="classes"></div>
</template><script setup>
import { computed, ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);const classes = computed(() => ({active: isActive.value,'text-danger': hasError.value
}));
</script>
解释:classes 根据 isActive 和 hasError 的值动态返回类对象。
2.3.2. Style 绑定示例
<template><div :style="styles"></div>
</template><script setup>
import { computed, ref } from 'vue';const color = ref('red');
const fontSize = ref(16);const styles = computed(() => ({color: color.value,fontSize: `${fontSize.value}px`
}));
</script>
解释:styles 根据 color 和 fontSize 的值动态返回样式对象。
4.注意事项
- 响应式:绑定的 Class 和 Style 会随数据变化自动更新。
- 与静态共存:静态的 class 和 style 可以与动态绑定共用。
<template><div class="static" :class="{ active: isActive }" style="color: blue" :style="{ fontSize: fontSize + 'px' }"></div>
</template>
结果:<div> 始终有 static 类和 color: blue,同时根据 isActive 和 fontSize 动态应用其他值。
三、watch和watchEffect用法细说:
1、watch:
3.1.1. 核心特点
-
显式指定监听的数据源(需手动列出依赖)。
-
惰性执行:默认不会立即执行回调,除非设置
immediate: true
。 -
适合场景:需要精确控制监听目标和回调逻辑
3.1.2. 基本语法
import { watch, ref } from 'vue';const data = ref(value);watch(source, // 监听的数据源(ref、reactive、getter 函数或数组)(newVal, oldVal) => { /* 回调逻辑 */ },{ immediate: false, // 是否立即执行回调deep: false // 是否深度监听对象/数组}
);
3.1.3. 代码示例
①监听单个数据源
<script setup>
import { ref, watch } from 'vue';const count = ref(0);// 监听 count 的变化
watch(count, (newVal, oldVal) => {console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});const increment = () => {count.value++;
};
</script><template><button @click="increment">+1</button>
</template>
②监听多个数据源
<script setup>
import { ref, watch } from 'vue';const x = ref(0);
const y = ref(0);// 监听多个 ref
watch([x, y], ([newX, newY], [oldX, oldY]) => {console.log(`坐标变化:(${oldX},${oldY}) → (${newX},${newY})`);
});
</script>
③深度监听对象
<script setup>
import { reactive, watch } from 'vue';const user = reactive({ name: 'Alice', address: { city: 'Beijing' }
});// 深度监听 user 对象
watch(user,(newVal) => {console.log('用户信息变化:', newVal);},{ deep: true }
);const changeCity = () => {user.address.city = 'Shanghai'; // 触发回调
};
</script>
④监听对象中某个特定属性
通过 getter 函数 明确指定要监听的属性,确保能精准追踪目标属性的变化。
<script setup>
import { reactive, watch } from 'vue';const obj = reactive({ a: 1, b: 2 });// 监听 obj.a 的变化
watch(() => obj.a, // getter 函数返回目标属性(newVal, oldVal) => {console.log(`obj.a 从 ${oldVal} 变为 ${newVal}`);}
);// 修改 obj.a 会触发回调
const changeA = () => {obj.a++;
};
</script><template><button @click="changeA">修改 obj.a</button>
</template>
注意:
- getter 函数 只是监听对象中某个指明的属性;比如;监听()=>obj.a;只有当obj.a变化了才会触发回调函数(obj.b变化了不触发);
- 深度监听{deep:true}:是监听整个对象及其所有嵌套属性;比如监听obj对象,对象的任意属性(包括嵌套)变化时均会触发;
newVal
和oldVal
相同(因为对象引用未变)
2、watchEffect
3.2.1. 核心特点
-
自动收集依赖:回调函数中使用的响应式数据会被自动追踪。
-
立即执行:回调函数会立即执行一次。
-
适合场景:依赖多个数据且不需要旧值的场景。
3.2.2. 基本语法
import { watchEffect } from 'vue';const stop = watchEffect((onCleanup) => {// 自动追踪依赖// 执行副作用逻辑return () => { // 清理逻辑(如取消请求)};
});// 手动停止监听
stop();
3.2.3. 代码示例
①自动跟着依赖
<script setup>
import { ref, watchEffect } from 'vue';const count = ref(0);
const double = ref(0);// 自动追踪 count 和 double
watchEffect(() => {double.value = count.value * 2;console.log(`double 值:${double.value}`);
});const increment = () => {count.value++;
};
</script>
②清理副作用
<script setup>
import { watchEffect } from 'vue';watchEffect((onCleanup) => {const timer = setInterval(() => {console.log('定时器运行中...');}, 1000);// 清理定时器(组件卸载或依赖变化时触发)onCleanup(() => {clearInterval(timer);console.log('定时器已清理');});
});
</script>
3.2.4. 注意事项
①避免无限循环
在回调中修改监听的数据可能导致无限循环:
// ❌ 错误:修改 count 会再次触发回调
watchEffect(() => {count.value++;
});
②异步操作处理
在 watchEffect
中处理异步操作时,使用 onCleanup
避免竞态条件:
watchEffect(async (onCleanup) => {let isValid = true;onCleanup(() => (isValid = false)); // 标记请求已过期const data = await fetchData();if (isValid) {// 处理有效数据}
});
③手动停止监听
在组件卸载时手动停止监听(尤其是 watchEffect
):
const stop = watchEffect(() => { /* ... */ });// 组件卸载时
onUnmounted(() => stop());
④性能优化
-
使用
flush: 'post'
确保回调在 DOM 更新后执行:watchEffect(() => { /* ... */ }, { flush: 'post' });
-
避免在
watch
中深度监听大型对象。
3、flush进一步解释:
`flush` 是 Vue 3 中 `watch` 和 `watchEffect` 的一个配置选项,用于控制**副作用函数(回调)的执行时机**。
简单来说,它决定了当数据变化时,监听的回调函数是立即执行,还是等 DOM 更新后再执行。
3.3.1.flush
的三种模式
-
'pre'
(默认值)-
触发时机:在组件更新前执行回调。
-
特点:此时 DOM 尚未更新,因此无法访问最新的 DOM 状态。
-
适用场景:需要在数据变化后、DOM 更新前执行逻辑(如验证数据或准备某些状态)。
-
-
'post'
-
触发时机:在组件更新后执行回调。
-
特点:此时 DOM 已更新,可以安全操作 DOM 或访问最新的渲染结果。
-
适用场景:需要依赖 DOM 更新的操作(如测量元素尺寸、触发第三方库的布局计算)。
-
-
'sync'
-
触发时机:同步触发回调,即依赖的数据变化后立即执行。
-
特点:可能导致回调被频繁触发(如一个操作修改多个依赖值),需注意性能问题。
-
适用场景:极少用,仅在需要极低延迟响应时使用(如实现复杂的同步逻辑)。
-
3.3.2.flush
的使用场景例子
①`flush: 'post'`(常用)
在 DOM 更新后执行操作(如聚焦输入框)
watchEffect(() => {// DOM 已更新,可以安全操作inputRef.value.focus();},{ flush: 'post' } // 确保 DOM 更新后执行);
②flush: 'pre'(默认值)
在组件更新前处理数据(如读取旧 DOM 状态)
watch(() => data.value,(newVal, oldVal) => {// 组件更新前记录旧 DOM 高度const oldHeight = element.clientHeight;},{ flush: 'pre' } // 默认值,可省略
);
③flush: 'sync'(特殊需求)
立即响应数据变化(如调试或实时同步数据)
watch(() => count.value,(newVal) => {console.log('Count 立即变化为:', newVal); // 同步输出},{ flush: 'sync' }
);
4、副作用的解释:
3.4.1:副作用的概述
-
副作用:在
watchEffect
回调中执行的异步或外部操作(如定时器、事件监听、请求等)。 -
清理函数:通过
onCleanup
注册的函数,用于在以下时机执行清理逻辑:-
依赖变化:当依赖的响应式数据变化,触发新的回调前。
-
组件卸载:当组件卸载时,自动清理所有未完成的副作用。
-
//基本语法
watchEffect((onCleanup) => {// 执行副作用(如定时器、请求)const timer = setInterval(() => {console.log('定时器运行中...');}, 1000);// 注册清理函数onCleanup(() => {clearInterval(timer); // 清理定时器console.log('定时器已清理');});
});
3.4.2:代码执行流程
①副作用产生
-
触发时机:
当组件挂载时,watchEffect
会立即执行回调函数。 -
操作内容:
在回调中创建了一个定时器timer
,它会每秒执行一次console.log('每秒执行一次')
。
②清理副作用
-
触发清理的时机:
-
依赖变化:如果回调中使用了响应式数据(如
ref
或reactive
),当这些数据变化时,Vue 会重新执行回调函数。在重新执行前,会先调用上一次注册的清理函数。 -
组件卸载:当组件被销毁时,Vue 会自动触发清理函数。
-
-
清理操作:
调用clearInterval(timer)
清除定时器,停止日志输出,避免内存泄漏。