Vue 3 组合式 API 实战:从 setup 到 Pinia 状态管理的完整演进
Vue 3 的组合式 API(Composition API)把"按选项组织代码"升级为"按逻辑组织代码",配合 Pinia 状态管理与 TypeScript,已成为现代 Vue 工程的标准范式。本文从核心概念到可复用模式,给出完整的实战指引。
一、为什么需要组合式 API
在 Options API 中,一个功能涉及的 data、computed、methods、watch 分散在四个选项中,组件越大越难维护:
// Options API:一个功能被拆散在四处
export default {
data() { return { articles: [], loading: false, page: 1 } },
computed: { totalPages() { ... } },
watch: { page() { this.fetch() } },
methods: { fetch() { ... }, next() { this.page++ } }
}
组合式 API 把同一功能的逻辑收拢到一组函数里,天然支持抽取复用:
<script setup lang="ts">
const { articles, loading, page, next, totalPages } = usePagedArticles()
</script>
二、setup 语法糖:最常用的形态
<script setup> 让组合式代码更加简洁:顶层声明即自动暴露给模板,无需 return:
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() { count.value++ }
</script>
<template>
<button @click="increment">{{ count }} × 2 = {{ doubled }}</button>
</template>
关键点:模板中自动解包 ref,脚本中必须写 .value;ref 用于标量/数组,reactive 用于嵌套对象——但实践中更推荐全程 ref(包括对象用 ref),配合 toRefs 解构,心智负担最小。
三、生命周期与响应式细节
3.1 组合式生命周期对照
| Options | Composition |
|---------|-------------|
| beforeCreate / created | 直接在 setup 中写同步逻辑 |
| mounted | onMounted(() => {...}) |
| beforeUnmount | onBeforeUnmount(() => {...}) |
<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
let timer: number | undefined
onMounted(() => { timer = setInterval(tick, 1000) })
onBeforeUnmount(() => clearInterval(timer)) // 清理定时器,防泄漏
</script>
3.2 watch 的三种写法
watch(count, (v, old) => {...}) // 监听单个 ref
watch([count, page], ([c, p]) => {...}) // 监听多个,值以数组返回
watch(() => props.filter, v => {...}) // 监听响应式对象的属性(getter 形式)
易错点:watch 一个 reactive 对象字段时必须用 getter 形式,直接传 props.filter 是普通值而非响应式源。
四、组合式函数的复用模式
组合式函数(Composable)是自定义 Hook,命名约定 useXxx:
// composables/usePagedArticles.ts
export function usePagedArticles(fetcher: (page: number) => Promise<Article[]>) {
const articles = ref<Article[]>([])
const page = ref(1)
const loading = ref(false)
const hasMore = ref(true)
async function load() {
if (loading.value || !hasMore.value) return
loading.value = true
try {
const items = await fetcher(page.value)
articles.value.push(...items)
hasMore.value = items.length > 0
page.value++
} finally {
loading.value = false
}
}
return { articles, loading, hasMore, load }
}
复用要点:组合式函数每次调用都是独立实例(闭包),天然支持并发场景;内部副作用(定时器、监听器)要在 onScopeDispose 中清理。
五、Pinia:Vue 3 官方状态管理
5.1 定义 Store(Setup 风格)
// stores/user.ts
export const useUserStore = defineStore('user', () => {
const token = ref('')
const profile = ref<UserProfile | null>(null)
const isLoggedIn = computed(() => !!token.value)
async function login(email: string, password: string) {
const { data } = await $fetch('/api/v1/auth/login', { method: 'POST', body: { email, password } })
token.value = data.access_token
profile.value = data.user
}
function logout() {
token.value = ''
profile.value = null
}
return { token, profile, isLoggedIn, login, logout }
})
5.2 在组件中使用
<script setup>
import { useUserStore } from '~/stores/user'
const user = useUserStore() // store 是响应式单例
// 直接访问 store 上的 ref(自动解包)
console.log(user.token, user.isLoggedIn)
</script>
Pinia 对比 Vuex 的核心优势:
- 去掉了 mutation,逻辑更直接,TypeScript 推导更友好;
- Store 即模块,无需 modules 嵌套;
- DevTools 支持时间旅行调试;
- 支持组合式写法,与 Composition API 无缝衔接。
六、状态持久化与 SSR 注意事项
6.1 持久化
// 手写简单持久化
export const useUserStore = defineStore('user', () => {
const token = ref(useCookie('token')) // Nuxt 中 cookie 即响应式又持久
...
})
6.2 SSR 下的三条铁律
- 客户端才读 cookie/localStorage:
process.client判断或useCookie封装; - 登录态不进 SSR 渲染:服务端恒为未登录,避免服务端与客户端状态串扰;
- 异步数据用 useAsyncData / useFetch:避免重复请求与闪烁。
七、实战清单
- 组件内逻辑超过 30 行就抽 composable;
- 所有异步请求返回 Promise,交给调用方 try/catch,不在 composable 里吞错误;
- ref 与 reactive 二选一:默认 ref,对象也用 ref,团队约定唯一;
- props 解构后用
toRefs保持响应式,或直接props.xxx; - watch 深层对象加
{ deep: true },但要意识到性能开销; - 大型表单用
reactive+ schema 校验库(zod/valibot),而非手工逐字段校验; v-model自定义组件用defineModel()(Vue 3.4+),告别手工 emit 样板。
结语
组合式 API 解决的不是"写法新不新"的问题,而是复杂组件状态的可维护性。把逻辑拆成高内聚的组合式函数、用 Pinia 管理跨组件状态、让 TypeScript 全程把关——这套组合拳打下来,Vue 工程在规模翻倍时依然能保持清晰的边界与可测试性。从今天开始,把每个组件都当作"可复用的逻辑单元"来组织,你会感受到组合式的真正魅力。
评论(0)
登录 后参与评论。