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

Vue 3 Composition API in Practice

Choyeon
Choyeon
Aug 21, 2026 1 min read 764 Views
346 words 2,031 chars Updated Nov 8, 2026
Vue 3 Composition API in Practice
Table of contents

Since Vue 3.0's release, the Composition API has evolved from "new feature" to Vue's default development paradigm. It solves the core pain points of Options API in large components — scattered logic and poor reusability — while preserving Vue's characteristic elegance and intuitiveness.

ref vs reactive: How to Choose?

This is the most common question from beginners. Essentially, reactive fits object-shaped state, while ref fits primitive types and objects that "need to be replaced wholesale."

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
  }
}

Two Responsive Declarations Compared

Dimension ref reactive
Primitive types supported ✅ string/number/boolean etc ❌ Objects/arrays only
Access pattern .value for read/write Direct property access
Destructuring behavior ✅ Retains reactivity ❌ Loses reactivity (needs toRefs)
Whole replacement obj.value = newObj safe Manual Object.assign merge
TS inference Intuitive Ref<T> Recursive unwrap confusing
Memory overhead Slightly higher (wrapper object) Slightly lower (direct Proxy wrap)

Three Golden Rules for Composable Extraction

The useProductFilter in the code example is a typical Composable. A good Composable should follow:

  1. Single Responsibility: Each Composable does one thing. Never write monstrosities like useProductFilterWithAuthAndAnalytics
  2. Explicit Returns: Use return { a, b, c } rather than returning reactive objects; let callers see exactly what they're using
  3. Lifecycle Symmetry: Resources claimed inside the Composable (watchers, timers, event subscriptions) must always be released in onBeforeUnmount

Avoid Common Performance Pitfalls

The Composition API is flexible but also full of pitfalls:

// ❌ Wrong: Recomputes huge array every render
const sorted = [...products.value].sort(heavySortFn)

// ✅ Correct: Cache result with computed
const sorted = computed(() => [...products.value].sort(heavySortFn))

// ❌ Wrong: Deep watching huge objects
watch(hugeObject, handler, { deep: true })

// ✅ Better: Watch only the field you care about
watch(() => hugeObject.items[0].status, handler)

Best Practices

  1. Wrap all derived state in computed — never write complex expressions inside templates
  2. Uniform naming: Composable functions use useXxx prefix; filenames match as useXxx.ts
  3. Combine Provide/Inject with Composables for unified global state management in root-level Provider components
  4. Don't put template-specific computation logic in Composables — keep them generally applicable
  5. Use unref() and isRef() to build utility functions that accept both Refs and plain values

Summary

The core value of the Composition API lies in "logical concern aggregation" and "fine-grained reuse granularity." Its real power manifests when you reorganize the data/methods/computed/watch scattered across the Options API era into testable, composable units organized by business domain. Mastering Composable design will bring a qualitative leap to your Vue code quality.

X微博Telegram

About the author

Poll

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

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

Comments (4)

0 / 3000
dryrun-test
dryrun-test15 d ago

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

摸鱼达人小王
摸鱼达人小王Sep 18, 2026

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

产品汪本汪
产品汪本汪pmdiary.ioSep 17, 2026

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

AI炼丹学徒
AI炼丹学徒Sep 17, 2026

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

Related posts

React 18 Hooks Best Practices

React 18 Hooks Best Practices

React 18 introduces revolutionary features like concurrent rendering, automatic batching, useTransition, and useDeferredValue. This article systematically covers correct useState/useEffect usage, useMemo/useCallback optimization boundaries, custom Hook design patterns, and a complete guide to avoiding stale closures, race conditions, and other pitfalls in concurrent mode.

1 min read
Tailwind CSS v4: Zero-config + CSS-native Variables

Tailwind CSS v4: Zero-config + CSS-native Variables

Tailwind CSS v4 is an architectural upgrade: goodbye tailwind.config.js, declare design tokens in @theme blocks inside native CSS files. Single @import "tailwindcss" entry replaces postcss.config; built-in CSS vars, native container queries, oxc engine 10-40x faster, simplified color functions.

1 min read
PWA Offline App in Practice: Service Worker + IndexedDB + Background Sync

PWA Offline App in Practice: Service Worker + IndexedDB + Background Sync

Build installable, offline-first Progressive Web Apps: Service Worker caching strategies (Cache First, Stale-While-Revalidate), IndexedDB wrapper for structured local data, Background Sync queue that replays offline operations when network returns. Full TypeScript code included.

1 min read
Complete Guide to CSS Container Queries

Complete Guide to CSS Container Queries

Modern responsive CSS pillars: Container Queries for component-level responsiveness based on parent size (not viewport), plus :has() parent selector for ancestor styling based on descendants.

1 min read
Astro Deep Dive: Islands Architecture & Content Collections

Astro Deep Dive: Islands Architecture & Content Collections

Astro innovations: Islands Architecture for minimal JS with partial hydration, Content Collections for type-safe Markdown/MDX management, ultra-fast zero-JS static sites.

1 min read
Frontend Performance: Core Web Vitals in Practice

Frontend Performance: Core Web Vitals in Practice

Practical Google Core Web Vitals playbook: LCP via preconnects/image-priority/CDN, CLS elimination via image dimensions/font-loading/placeholders, INP via long-task splitting + debouncing.

1 min read