选项式 API vs 组合式 API 深度对比
除了写法不同,选项式 API 和组合式 API 在设计理念、逻辑组织、类型支持、复用能力等方面都有本质区别。
1. 设计理念和思维模式的不同
选项式 API:基于"选项"的分类思维
// 选项式 API - 按功能类型分类
export default {
// 数据相关放一起
data() {
return {
users: [],
loading: false,
searchQuery: ''
}
},
// 计算属性放一起
computed: {
filteredUsers() {
return this.users.filter(user =>
user.name.includes(this.searchQuery)
)
}
},
// 方法放一起
methods: {
async fetchUsers() {
this.loading = true
this.users = await api.getUsers()
this.loading = false
},
updateQuery(query) {
this.searchQuery = query
}
},
// 生命周期放一起
mounted() {
this.fetchUsers()
}
}
组合式 API:基于"逻辑关注点"的聚合思维
<script setup>
import { ref, computed, onMounted } from 'vue'
// 用户搜索逻辑 - 相关的代码放在一起
const searchQuery = ref('')
const users = ref([])
const loading = ref(false)
const filteredUsers = computed(() => {
return users.value.filter(user =>
user.name.includes(searchQuery.value)
)
})
const fetchUsers = async () => {
loading.value = true
users.value = await api.getUsers()
loading.value = false
}
const updateQuery = (query) => {
searchQuery.value = query
}
// 生命周期也跟相关逻辑放在一起
onMounted(() => {
fetchUsers()
})
// 另一个独立的逻辑关注点可以放在下面
const otherFeature = () => {
// 相关状态和方法都在一起
}
</script>
2. 逻辑组织和复用能力的本质区别
选项式 API 的逻辑复用问题
// mixins/userMixin.js - 混入方式(容易冲突)
export default {
data() {
return {
users: [],
userLoading: false
}
},
methods: {
async fetchUsers() {
this.userLoading = true
this.users = await api.getUsers()
this.userLoading = false
}
},
mounted() {
this.fetchUsers()
}
}
// ComponentA.vue - 使用混入
export default {
mixins: [userMixin],
data() {
return {
// 可能跟混入中的 users 冲突
products: []
}
},
// 逻辑分散在不同选项中,难以追踪
}
组合式 API 的逻辑复用优势
// composables/useUsers.js - 组合式函数
export function useUsers() {
const users = ref([])
const loading = ref(false)
const fetchUsers = async () => {
loading.value = true
users.value = await api.getUsers()
loading.value = false
}
onMounted(() => {
fetchUsers()
})
return {
users,
loading,
fetchUsers
}
}
// ComponentA.vue - 使用组合式函数
<script setup>
import { useUsers } from '@/composables/useUsers'
const { users, loading, fetchUsers } = useUsers()
// 可以同时使用多个组合式函数,不会冲突
const { products, fetchProducts } = useProducts()
</script>
3. 响应式系统的使用差异
选项式 API 的响应式
export default {
data() {
return {
user: {
name: 'John',
profile: {
age: 25
}
},
items: [1, 2, 3]
}
},
methods: {
updateUser() {
// Vue 2 中需要特殊处理数组和对象
this.user.profile.age = 26 // 响应式更新
this.items[0] = 999 // Vue 2 中不是响应式的!
// Vue 2 的正确做法
this.$set(this.items, 0, 999)
this.items.splice(0, 1, 999)
}
}
}
组合式 API 的响应式
<script setup>
import { ref, reactive } from 'vue'
const user = reactive({
name: 'John',
profile: {
age: 25
}
})
const items = ref([1, 2, 3])
const updateUser = () => {
user.profile.age = 26 // 响应式更新
items.value[0] = 999 // 响应式更新
// 更灵活的响应式操作
const newItem = ref(100)
items.value.push(newItem.value)
}
</script>
4. TypeScript 支持程度的巨大差异
选项式 API 的 TypeScript 支持有限
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
count: 0, // 类型可以推断为 number
user: null as User | null // 需要类型断言
}
},
computed: {
// 计算属性的类型声明比较麻烦
doubleCount(): number {
return this.count * 2
}
},
methods: {
// 方法参数和返回值的类型声明
updateUser(user: User): void {
this.user = user
}
},
// 生命周期钩子没有很好的类型提示
mounted() {
// this.$ 上的属性类型支持有限
}
})
组合式 API 的完整 TypeScript 支持
<script setup lang="ts">
import { ref, computed } from 'vue'
interface User {
id: number
name: string
email: string
}
// 完整的类型推断
const count = ref(0) // 自动推断为 Ref<number>
const user = ref<User | null>(null) // 明确的类型
// 计算属性的完整类型支持
const doubleCount = computed(() => count.value * 2) // 自动推断为 ComputedRef<number>
// 函数的完整类型支持
const updateUser = (newUser: User) => {
user.value = newUser
}
// 自动补全和类型检查
user.value?.name // 完整的智能提示
</script>
5. 代码组织和维护性的对比
复杂组件在选项式 API 中的问题
export default {
data() {
return {
// 多个功能的变量混在一起
users: [],
products: [],
orders: [],
userLoading: false,
productLoading: false,
orderLoading: false,
searchQuery: '',
filterStatus: '',
pagination: { page: 1, limit: 20 }
}
},
computed: {
// 多个功能的计算属性混在一起
filteredUsers() { /* ... */ },
filteredProducts() { /* ... */ },
filteredOrders() { /* ... */ },
paginatedUsers() { /* ... */ },
paginatedProducts() { /* ... */ }
},
methods: {
// 多个功能的方法混在一起
fetchUsers() { /* ... */ },
fetchProducts() { /* ... */ },
fetchOrders() { /* ... */ },
searchUsers() { /* ... */ },
searchProducts() { /* ... */ }
},
mounted() {
// 多个功能的初始化混在一起
this.fetchUsers()
this.fetchProducts()
this.fetchOrders()
}
}
组合式 API 的逻辑分离优势
<script setup>
import { useUsers } from './composables/useUsers'
import { useProducts } from './composables/useProducts'
import { useOrders } from './composables/useOrders'
// 每个功能独立,清晰分离
//从 `useUsers` 函数的返回值中提取出 `users`、`userLoading`、`filteredUsers`、`fetchUsers`、`searchUsers` 这些属性
const {
users,
userLoading,
filteredUsers,
fetchUsers,
searchUsers
} = useUsers()
const {
products,
productLoading,
filteredProducts,
fetchProducts,
searchProducts
} = useProducts()
const {
orders,
orderLoading,
filteredOrders,
fetchOrders
} = useOrders()
// 初始化各个功能
onMounted(() => {
fetchUsers()
fetchProducts()
fetchOrders()
})
</script>
6. 学习曲线和心智模型
选项式 API 的学习曲线
// 相对平缓,符合传统 OOP 思维
export default {
props: ['message'], // 输入
data() { // 状态
return { count: 0 }
},
computed: { // 派生状态
double() { return this.count * 2 }
},
methods: { // 方法
increment() { this.count++ }
},
watch: { // 副作用
count(newVal) { console.log(newVal) }
},
mounted() { // 生命周期
console.log('组件挂载')
}
}
组合式 API 的学习曲线
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
// 需要理解响应式基础
const count = ref(0)
const double = computed(() => count.value * 2)
// 需要理解作用域和闭包
const increment = () => {
count.value++
}
// 需要理解生命周期注册
onMounted(() => {
console.log('组件挂载')
})
// 需要理解侦听器机制
watch(count, (newVal) => {
console.log(newVal)
})
</script>
7. 性能优化的差异
选项式 API 的性能优化
export default {
data() {
return {
largeList: [] // 整个组件重新渲染
}
},
methods: {
updateItem(index, newValue) {
// 需要特殊优化手段
this.$set(this.largeList, index, newValue)
}
}
}
组合式 API 的性能优化
<script setup>
import { ref, shallowRef, markRaw } from 'vue'
// 更细粒度的响应式控制
const largeList = shallowRef([]) // 浅层响应式
const heavyObject = markRaw({ // 非响应式
// 大型静态数据
})
// 更精确的更新控制
const updateItem = (index, newValue) => {
const newList = [...largeList.value]
newList[index] = newValue
largeList.value = newList
}
</script>
8. 与第三方库集成的差异
选项式 API 的集成
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState(['user', 'settings']),
localComputed() { /* ... */ }
},
methods: {
...mapActions(['login', 'logout']),
localMethod() { /* ... */ }
}
// 混合了 Vuex 和本地逻辑,难以区分
}
组合式 API 的集成
<script setup>
import { useStore } from 'vuex'
import { useLocalLogic } from './composables/useLocalLogic'
// 清晰的分离
const store = useStore()
const user = computed(() => store.state.user)
const login = () => store.dispatch('login')
// 本地逻辑
const { localData, localMethod } = useLocalLogic()
</script>
9. 实际项目中的选择建议
适合选项式 API 的场景
// 1. 简单的展示组件
export default {
props: ['title', 'content'],
template: `
<div class="card">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
`
}
// 2. 迁移 Vue 2 项目
// 3. 团队对选项式 API 更熟悉
// 4. 不需要复杂逻辑复用
适合组合式 API 的场景
<script setup>
// 1. 复杂的业务组件
const {
data,
pagination,
filters,
search,
loadData
} = useDataTable()
const {
form,
validation,
submit,
reset
} = useFormHandler()
// 2. 需要逻辑复用的场景
// 3. TypeScript 项目
// 4. 大型应用开发
</script>
总结
特性 |
选项式 API |
组合式 API |
设计理念 |
按选项分类 |
按逻辑关注点聚合 |
逻辑复用 |
Mixins(有冲突风险) |
组合式函数(无冲突) |
TypeScript |
有限支持 |
完整支持 |
代码组织 |
功能分散在不同选项 |
相关逻辑集中 |
响应式 |
自动处理,但有限制 |
显式声明,更灵活 |
学习曲线 |
平缓 |
较陡峭 |
性能优化 |
组件级别 |
更细粒度控制 |
适用场景 |
简单组件、Vue 2 迁移 |
复杂组件、大型项目 |
核心区别: 选项式 API 是"怎么做",组合式 API 是"做什么"。选择哪种取决于项目复杂度、团队习惯和具体需求。