Vue 3 Composition API 介绍
为什么有 Composition API
Vue 2 的 Options API(data、methods、computed、watch)在组件变得复杂时,逻辑被分散到各个选项中,导致:
- 逻辑关注点分散:同一功能的代码被拆分到 data、methods、watch 中
- 逻辑复用困难:mixins 存在命名冲突和来源不明的问题
- TypeScript 支持不佳:this 上的属性类型推断困难
Composition API 通过一组组合式函数解决了这些问题。
setup 函数
setup 是 Composition API 的入口,在组件创建之前执行。
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
function increment() {
count.value++;
}
onMounted(() => {
console.log('组件已挂载');
});
return { count, increment };
}
};
</script>
ref 与 reactive
ref
ref 用于创建响应式的基本类型值,返回一个包含 .value 属性的对象。
import { ref } from 'vue';
const count = ref(0);
count.value++; // 必须通过 .value 操作
// 模板中自动解包
// <p>{{ count }}</p>
reactive
reactive 用于创建响应式对象,直接访问属性。
import { reactive } from 'vue';
const user = reactive({
name: '张三',
age: 25,
hobbies: ['coding', 'reading']
});
user.age++; // 直接修改
选择原则
- ref:基本类型值、从 API 返回的单个值、需要被解构的值
- reactive:深层嵌套的对象、表单数据模型
computed 与 watch
computed
import { ref, computed } from 'vue';
const price = ref(100);
const quantity = ref(2);
const total = computed(() => price.value * quantity.value);
// 可写的 computed
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
[firstName.value, lastName.value] = val.split(' ');
}
});
watch
import { watch, ref } from 'vue';
const searchQuery = ref('');
// 监听单个源
watch(searchQuery, (newVal, oldVal) => {
console.log(`搜索词从 "${oldVal}" 变为 "${newVal}"`);
});
// 监听多个源
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`${newFirst} ${newLast}`);
});
// 深度监听
watch(() => state.obj, (newVal) => { ... }, { deep: true });
// 立即执行
watch(source, callback, { immediate: true });
生命周期钩子
| Options API | Composition API |
|---|---|
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
import { onMounted, onUnmounted, onBeforeUpdate } from 'vue';
setup() {
onMounted(() => { /* 获取数据 */ });
onUnmounted(() => { /* 清理 */ });
onBeforeUpdate(() => { /* 更新前逻辑 */ });
}
逻辑复用:自定义组合式函数
// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() {
const x = ref(0);
const y = ref(0);
function update(event) {
x.value = event.pageX;
y.value = event.pageY;
}
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
// 在组件中使用
// const { x, y } = useMouse();
// useFetch.js
import { ref, watchEffect } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
watchEffect(async () => {
loading.value = true;
try {
const res = await fetch(url.value);
data.value = await res.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
});
return { data, error, loading };
}
script setup 语法糖
Vue 3.2+ 推荐的简洁写法:
<script setup>
import { ref, computed } from 'vue';
import { useMouse } from './useMouse';
const { x, y } = useMouse();
const count = ref(0);
const double = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
<template>
<p>鼠标位置: {{ x }}, {{ y }}</p>
<p>计数: {{ count }}(两倍: {{ double }})</p>
<button @click="increment">增加</button>
</template>
总结
Composition API 提供了更灵活的逻辑组织方式,配合 <script setup> 语法糖,代码简洁且易于维护。它不是要完全取代 Options API,而是在复杂场景下提供了更优的选择。建议新项目使用 <script setup>,大型组件使用 Composition API 组织逻辑。