为什么选择组合式 API?
Vue 3 的组合式 API(Composition API)是一种新的组件编写方式,相比 Options API,它提供了更好的代码组织方式和逻辑复用能力。
基础对比
Options API
<script>
export default {
data() {
return {
count: 0,
name: ''
};
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
watch: {
count(newVal) {
console.log('count changed:', newVal);
}
}
};
</script>Composition API
<script setup>
import { ref, computed, watch } from 'vue';
const count = ref(0);
const name = ref('');
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
watch(count, (newVal) => {
console.log('count changed:', newVal);
});
</script>逻辑复用:自定义 Hooks
组合式 API 最大的优势之一是逻辑复用。
创建可复用的逻辑
// useCounter.js
import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function reset() {
count.value = initialValue;
}
return {
count,
doubleCount,
increment,
decrement,
reset
};
}使用自定义 Hook
<script setup>
import { useCounter } from './useCounter';
const { count, doubleCount, increment, decrement } = useCounter(10);
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>常用 Hooks 示例
useFetch
import { ref, watchEffect } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
data.value = await response.json();
} catch (e) {
error.value = e.message;
} finally {
loading.value = false;
}
}
watchEffect(() => {
fetchData();
});
return { data, error, loading, refetch: fetchData };
}useLocalStorage
import { ref, watch } from 'vue';
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key);
const initial = stored ? JSON.parse(stored) : defaultValue;
const value = ref(initial);
watch(value, (newVal) => {
localStorage.setItem(key, JSON.stringify(newVal));
}, { deep: true });
return value;
}useDebounce
import { ref, watch } from 'vue';
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value);
let timeout = null;
watch(value, (newVal) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
debouncedValue.value = newVal;
}, delay);
});
return debouncedValue;
}代码组织
按功能组织
src/
├── composables/
│ ├── useAuth.js
│ ├── useFetch.js
│ ├── useCounter.js
│ └── index.js
├── components/
│ ├── UserCard.vue
│ └── Counter.vue
└── views/
├── Home.vue
└── Profile.vue按领域组织
src/
├── features/
│ ├── auth/
│ │ ├── useAuth.js
│ │ ├── useLogin.js
│ │ └── components/
│ │ └── LoginForm.vue
│ └── counter/
│ ├── useCounter.js
│ └── components/
│ └── Counter.vue最佳实践
1. 使用 `<script setup>`
<script setup>
// 推荐的写法
import { ref } from 'vue';
const count = ref(0);
</script>2. 合理使用 ref 和 reactive
// 基本类型用 ref
const count = ref(0);
// 对象类型可以用 reactive
const user = reactive({
name: 'John',
age: 30
});
// 或者统一用 ref
const user = ref({
name: 'John',
age: 30
});3. 使用 toRefs 解构
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
name: ''
});
// 解构后保持响应性
const { count, name } = toRefs(state);迁移建议
1. 渐进式迁移
不需要一次性迁移所有组件,可以:
2. 迁移步骤
data 改为 ref 或 reactivecomputed 改为 computed()methods 改为普通函数watch 改为 watch()总结
组合式 API 的优势:
记住,Composition API 不是要取代 Options API,而是提供更多的选择。根据项目规模和团队习惯选择合适的 API。