Vue 3 Composition API 最佳实践

为什么选择 Composition API

Vue 3 的 Composition API 解决了 Options API 在大型组件中的代码组织问题:

  • 逻辑复用:Composables 替代 Mixins
  • 类型推导:天然支持 TypeScript
  • 代码组织:相关逻辑集中在一起

核心 API 速查

ref vs reactive

import { ref, reactive } from 'vue';

// ref:适用于基本类型,也可包装对象
const count = ref(0);
console.log(count.value); // 0

// reactive:适用于对象,自动解包
const state = reactive({
  name: '张三',
  age: 25,
});
console.log(state.name); // '张三'

选择建议:

  • 基本类型 → ref
  • 对象/数组 → reactive 或 ref(ref 会自动解包对象)
  • 需要整体替换 → ref

computed

import { computed, ref } from 'vue';

const firstName = ref('张');
const lastName = ref('三');

// 计算属性
const fullName = computed(() => `${firstName.value}${lastName.value}`);

// 可写的计算属性
const fullNameWritable = computed({
  get: () => `${firstName.value}${lastName.value}`,
  set: (val: string) => {
    firstName.value = val[0];
    lastName.value = val.slice(1);
  },
});

watch & watchEffect

import { watch, watchEffect, ref } from 'vue';

const keyword = ref('');

// watch:明确指定要监听的数据
watch(keyword, (newVal, oldVal) => {
  console.log(`搜索词从 "${oldVal}" 变为 "${newVal}"`);
}, { debounce: 300 }); // 需要自定义 debounce

// watchEffect:自动追踪依赖
watchEffect(() => {
  // 自动收集 keyword 作为依赖
  fetchSearchResults(keyword.value);
});

自定义 Composables

// composables/useFetch.ts
import { ref, watchEffect, toValue, type Ref } from 'vue';

interface UseFetchReturn<T> {
  data: Ref<T | null>;
  error: Ref<string | null>;
  loading: Ref<boolean>;
}

export function useFetch<T>(url: string | Ref<string>): UseFetchReturn<T> {
  const data = ref<T | null>(null) as Ref<T | null>;
  const error = ref<string | null>(null);
  const loading = ref(false);

  watchEffect(async () => {
    loading.value = true;
    try {
      const response = await fetch(toValue(url));
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      data.value = await response.json();
    } catch (e) {
      error.value = (e as Error).message;
    } finally {
      loading.value = false;
    }
  });

  return { data, error, loading };
}

使用:

<script setup lang="ts">
import { useFetch } from '@/composables/useFetch';

const { data: posts, loading, error } = useFetch<Post[]>('/api/posts');
</script>

常见陷阱

1. reactive 解构丢失响应性

const state = reactive({ count: 0 });

// 错误:解构后丢失响应性
const { count } = state;

// 正确:使用 toRefs
import { toRefs } from 'vue';
const { count } = toRefs(state);

2. ref 在模板中自动解包

<template>
  <!-- 正确:模板中自动解包,不需要 .value -->
  <p>{{ count }}</p>
</template>

总结

Composition API 不仅是一种新的写法,更是更好的代码组织方式。合理使用 Composables,可以让你的 Vue 代码更清晰、更易维护。