提示:本网站现在为维护调试状态

Vue 3 组合式 API 实战

Choyeon
Choyeon
2026年8月21日 1 分钟阅读 765 阅读
601 字 869 字符 更新于 2026年11月8日
Vue 3 组合式 API 实战
目录

自 Vue 3.0 发布以来,组合式 API 已经从"新特性"演变为 Vue 的默认开发范式。它解决了 Options API 在大型组件中逻辑分散、难以复用的核心痛点,同时保留了 Vue 一贯的优雅和直观。

ref 与 reactive:如何选择?

这是新手最常问的问题。本质上,reactive 适合对象型状态,ref 适合原始类型与"需要整体替换"的对象。

import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'

export interface Product {
  id: number
  name: string
  price: number
  category: string
  tags: string[]
  rating: number
  inStock: boolean
}

export interface FilterOptions {
  keyword: string
  categories: string[]
  priceRange: [number, number]
  minRating: number
  inStockOnly: boolean
}

export function useProductFilter(initialProducts: Product[]) {
  const products = ref<Product[]>(initialProducts)
  const loading = ref(false)
  const error = ref<string | null>(null)

  const filters = reactive<FilterOptions>({
    keyword: '',
    categories: [],
    priceRange: [0, 10000],
    minRating: 0,
    inStockOnly: false
  })

  const filteredProducts = computed(() => {
    let result = [...products.value]

    if (filters.keyword) {
      const kw = filters.keyword.toLowerCase()
      result = result.filter(p => p.name.toLowerCase().includes(kw))
    }

    if (filters.categories.length > 0) {
      result = result.filter(p => filters.categories.includes(p.category))
    }

    result = result.filter(p =>
      p.price >= filters.priceRange[0] &&
      p.price <= filters.priceRange[1]
    )

    if (filters.minRating > 0) {
      result = result.filter(p => p.rating >= filters.minRating)
    }

    if (filters.inStockOnly) {
      result = result.filter(p => p.inStock)
    }

    return result
  })

  const stats = computed(() => ({
    total: filteredProducts.value.length,
    maxPrice: Math.max(0, ...filteredProducts.value.map(p => p.price)),
    avgRating: filteredProducts.value.length
      ? filteredProducts.value.reduce((s, p) => s + p.rating, 0) / filteredProducts.value.length
      : 0
  }))

  function resetFilters() {
    filters.keyword = ''
    filters.categories = []
    filters.priceRange = [0, 10000]
    filters.minRating = 0
    filters.inStockOnly = false
  }

  function toggleCategory(cat: string) {
    const i = filters.categories.indexOf(cat)
    i >= 0 ? filters.categories.splice(i, 1) : filters.categories.push(cat)
  }

  let unwatch: (() => void) | null = null
  onMounted(() => {
    unwatch = watch(
      () => [filters.keyword, filters.priceRange[1]],
      async () => {
        // 模拟统计上报
        console.log('[analytics] filter changed')
      },
      { deep: false, flush: 'post' }
    )
  })

  onBeforeUnmount(() => {
    unwatch?.()
  })

  return {
    products,
    loading,
    error,
    filters,
    filteredProducts,
    stats,
    resetFilters,
    toggleCategory
  }
}

两种响应式声明对比

维度 ref reactive
支持原始类型 ✅ string/number/boolean 等 ❌ 仅支持对象/数组
访问方式 .value 读写 直接读写属性
解构赋值 ✅ 保持响应性 ❌ 丢失响应性(需 toRefs)
整体替换 obj.value = newObj 安全 Object.assign 手动合并
TS 类型推断 直观 Ref<T> 递归 unwrap 易混淆
内存开销 稍高(包装对象) 略低(Proxy 直接包裹)

Composable 抽取的三条黄金原则

代码示例中的 useProductFilter 就是一个典型的 Composable。好的 Composable 应该遵循:

  1. 单一职责:每个 Composable 只做一件事。不要写出 useProductFilterWithAuthAndAnalytics 这样的怪物
  2. 显式返回值:使用 return { a, b, c } 而非返回 reactive 对象,让调用方知道自己用了什么
  3. 生命周期对称:在 Composable 内部申请的资源(监听、定时器、事件订阅)一定要在 onBeforeUnmount 中释放

避免常见的性能陷阱

组合式 API 虽然灵活,但也容易踩坑:

// ❌ 错误:每次渲染都重新计算巨大数组
const sorted = [...products.value].sort(heavySortFn)

// ✅ 正确:用 computed 缓存结果
const sorted = computed(() => [...products.value].sort(heavySortFn))

// ❌ 错误:watch 开启 deep 监听大型对象
watch(hugeObject, handler, { deep: true })

// ✅ 更好:只监听关心的字段
watch(() => hugeObject.items[0].status, handler)

最佳实践

  1. 用 computed 包装所有派生状态,不要在 template 里写复杂表达式
  2. 组合式函数命名统一 useXxx 前缀,文件命名也对应 useXxx.ts
  3. Provide/Inject 搭配 Composable 使用,在根级 Provider 组件中统一管理全局状态
  4. 不要把 template 专用的计算逻辑放在 Composable 里,保持它的普适性
  5. 使用 unref() 和 isRef() 编写既能接受 Ref 又能接受普通值的工具函数

总结

组合式 API 的核心价值在于"逻辑关注点聚合"与"复用粒度精细"。把 Options API 时代散落的 data/methods/computed/watch 按业务领域重新组织成一个个可测试、可组合的单元,才是它真正的威力所在。掌握 Composable 的设计,你的 Vue 代码质量会迎来质的飞跃。

X微博Telegram

本文作者

投票

你最希望 Rosetta 优先加入哪项功能?

社区投票(演示数据,可后台删除)

评论 (4)

0 / 3000
dryrun-test
dryrun-test15天前

dry-run 端到端验证评论第二条:确认日志落盘且零投递。

摸鱼达人小王
摸鱼达人小王2026年9月18日

建议可以加一下关于无障碍访问(a11y)的部分,现在很多项目都不重视这块,但真的很重要。

产品汪本汪
产品汪本汪pmdiary.io2026年9月17日

示例代码里那个 useEffect 的依赖数组好像少了个参数,严格模式下会触发两次,建议修正一下。

AI炼丹学徒
AI炼丹学徒2026年9月17日

作为一个后端转全栈的,看你的前端文章从来不会觉得难懂,讲得特别接地气,谢谢!

相关文章

React 18 Hooks 最佳实践

React 18 Hooks 最佳实践

React 18 引入了并发渲染、自动批处理、useTransition、useDeferredValue 等革命性特性。本文系统梳理 useState/useEffect 的正确用法、useMemo/useCallback 的优化边界、自定义 Hook 的设计模式,以及并发模式下避免陈旧闭包、竞态条件等陷阱的完整指南,帮助你写出更健壮的 React 18 代码。

1 分钟阅读
Tailwind CSS v4 新特性:Zero-config + CSS-native 变量

Tailwind CSS v4 新特性:Zero-config + CSS-native 变量

Tailwind CSS v4 是一次架构级升级:彻底抛弃 tailwind.config.js,用 @theme 块在原生 CSS 文件声明设计令牌;新增 @import "tailwindcss" 单入口代替 postcss.config;内置 CSS 变量、容器查询原生支持、oxc 引擎提速 10-40 倍、颜色函数简化。

1 分钟阅读
PWA 离线应用实战:Service Worker + IndexedDB + Background Sync

PWA 离线应用实战:Service Worker + IndexedDB + Background Sync

逐步构建可安装、可离线运行、网络恢复自动同步的 Progressive Web App:Service Worker 缓存策略(Cache First / Stale-While-Revalidate)、IndexedDB 封装结构化本地数据、Background Sync 后台队列在用户离线操作后网络恢复时自动提交变更,附完整 TS 代码。

1 分钟阅读
CSS Container Queries 完全指南

CSS Container Queries 完全指南

彻底掌握现代响应式 CSS:Container Queries 让组件基于父容器尺寸自适应(而非视口),配合 :has() 父选择器实现依赖后代状态的样式变化,彻底改变组件化响应式设计范式。

1 分钟阅读
Astro 静态站点深度:群岛架构 & 内容集合

Astro 静态站点深度:群岛架构 & 内容集合

深入理解 Astro 核心创新:群岛架构实现最小化 JS 注入,仅交互组件按需水合;内容集合提供类型安全的 Markdown/MDX 管理,构建零 JS 的高性能静态站点。

1 分钟阅读
前端性能优化:Core Web Vitals 实战

前端性能优化:Core Web Vitals 实战

Google 搜索排名核心指标完全实战:LCP 最大内容绘制(预连接/图像优先级/CDN缓存)、CLS 累积布局偏移(图像尺寸/字体FOIT/动态占位)、INP 交互响应(长任务拆分/防抖节流)全面落地。

1 分钟阅读