Vue 3 Composition API 介绍

2026-06-22 · 6 阅读 · 539字
TypeScriptVue

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 组织逻辑。