为什么用组合式 API
Vue 2 的 Options API 把同一功能的逻辑分散在 data、methods、watch、computed 等选项里,组件一复杂,”一个功能的相关代码被拆得七零八落”的问题就很明显。Vue 3 的组合式 API(Composition API)允许我们按逻辑关注点组织代码,还能把可复用的逻辑抽成独立函数。
script setup 语法糖
从 Vue 3.2 开始,推荐使用 <script setup>,它让组合式 API 的写法更简洁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { ref, computed } from 'vue'
// 顶层声明的变量、函数自动暴露给模板 const count = ref(0)
function increment() { count.value++ } </script>
<template> <button @click="increment">count = {{ count }}</button> </template>
|
使用 <script setup> 后不需要 export default,也不需要 return,组件自动变成”开箱即用”。
核心 API 速览
ref 与 reactive
1 2 3 4 5 6 7 8 9 10 11 12
| import { ref, reactive } from 'vue'
const name = ref('张三') console.log(name.value)
const user = reactive({ name: '张三', age: 25 }) user.age++
|
一个容易踩的坑:reactive 对解构后的属性会丢失响应性:
1 2 3 4 5 6
| const { age } = user
import { toRefs } from 'vue' const { age } = toRefs(user)
|
computed 计算属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { ref, computed } from 'vue'
const price = ref(100) const quantity = ref(2)
const total = computed(() => price.value * quantity.value)
const totalEditable = computed({ get: () => price.value * quantity.value, set: (val) => { price.value = val / quantity.value } })
|
watch 侦听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { ref, watch, watchEffect } from 'vue'
const keyword = ref('')
watch(keyword, (newVal, oldVal) => { console.log('搜索词变化:', oldVal, '->', newVal) })
watch([keyword, page], ([newK, newP], [oldK, oldP]) => { })
watch( () => user.age, (val) => { console.log('年龄变为', val) } )
watchEffect(() => { console.log('当前搜索词:', keyword.value) })
|
注意:watch 是惰性的(依赖变化才触发),watchEffect 一进来就会执行一次。
生命周期
组合式 API 中生命周期钩子统一改为 onXxx 形式,且只能在 setup 顶层调用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => { })
onUpdated(() => { })
onUnmounted(() => { })
|
自定义组合函数:逻辑复用的核心
把可复用的逻辑抽成以 use 开头的函数,这是组合式 API 最大的价值。
下面封装一个”计数器”组合函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) { const count = ref(initialValue) const double = computed(() => count.value * 2)
function increment() { count.value++ }
function reset() { count.value = initialValue }
return { count, double, increment, reset } }
|
组件中直接使用:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { useCounter } from './useCounter'
const { count, double, increment, reset } = useCounter(10) </script>
<template> <p>count: {{ count }},double: {{ double }}</p> <button @click="increment">+1</button> <button @click="reset">重置</button> </template>
|
再比如常见的”请求数据”组合函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { ref } from 'vue'
export function useRequest(fetcher) { const data = ref(null) const loading = ref(false) const error = ref(null)
async function run(...args) { loading.value = true error.value = null try { data.value = await fetcher(...args) } catch (e) { error.value = e } finally { loading.value = false } }
return { data, loading, error, run } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> import { useRequest } from './useRequest'
const { data, loading, error, run } = useRequest((id) => fetch(`/api/user/${id}`).then((res) => res.json()) )
run(1) // 页面加载时触发 </script>
<template> <div v-if="loading">加载中...</div> <div v-else-if="error">出错了:{{ error.message }}</div> <div v-else>{{ data }}</div> </template>
|
组合式 API 的几个注意点
- 只能在
<script setup> 或 setup() 顶层调用,不能在 if、for 里调用响应式 API;
ref 在模板中自动解包,写 {{ count }} 而不是 {{ count.value }};在 JS 中必须写 .value;
- 组合函数内部如果创建了定时器、事件监听,记得在
onUnmounted 里清理,避免内存泄漏;
- 项目级复用逻辑放到
src/composables/ 目录统一管理。
总结
组合式 API 的核心价值是按逻辑组织代码 + 函数级复用。先用好 ref / reactive / computed / watch 这四件套,再养成分层抽取 useXxx 组合函数的习惯,组件会越写越清晰。