🔍 登录 注册

Vue 3 组合式 API 实战:从 setup 到 Pinia 状态管理的完整演进

小 小卒子 2026-10-20 前端 👁 1 ⏱ 6min

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 下的三条铁律

  1. 客户端才读 cookie/localStorage:process.client 判断或 useCookie 封装;
  2. 登录态不进 SSR 渲染:服务端恒为未登录,避免服务端与客户端状态串扰;
  3. 异步数据用 useAsyncData / useFetch:避免重复请求与闪烁。

七、实战清单

  1. 组件内逻辑超过 30 行就抽 composable;
  2. 所有异步请求返回 Promise,交给调用方 try/catch,不在 composable 里吞错误;
  3. ref 与 reactive 二选一:默认 ref,对象也用 ref,团队约定唯一;
  4. props 解构后用 toRefs 保持响应式,或直接 props.xxx;
  5. watch 深层对象加 { deep: true },但要意识到性能开销;
  6. 大型表单用 reactive + schema 校验库(zod/valibot),而非手工逐字段校验;
  7. v-model 自定义组件用 defineModel()(Vue 3.4+),告别手工 emit 样板。

结语

组合式 API 解决的不是"写法新不新"的问题,而是复杂组件状态的可维护性。把逻辑拆成高内聚的组合式函数、用 Pinia 管理跨组件状态、让 TypeScript 全程把关——这套组合拳打下来,Vue 工程在规模翻倍时依然能保持清晰的边界与可测试性。从今天开始,把每个组件都当作"可复用的逻辑单元"来组织,你会感受到组合式的真正魅力。

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © 小卒子

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章