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

Vue 3 コンポジション API 実践

Choyeon
Choyeon
2026年8月21日 1 分で読了 767 閲覧
346 字 2,031 文字 更新日 2026年11月8日
Vue 3 コンポジション API 実践
目次

Vue 3 Composition API in Practice

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

筆者について

投票

你最希望 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 の設計パターン、コンカレントモードでの古いクロージャや競合状態を回避するための完全なガイドを提供します。

1 分で読了
Tailwind CSS v4 新機能:Zero-config + CSS ネイティブ変数

Tailwind CSS v4 新機能:Zero-config + CSS ネイティブ変数

Tailwind CSS v4はアーキテクチャ級アップデート:tailwind.config.js を廃止し、ネイティブCSS内の@themeブロックでデザイントークン宣言。@import "tailwindcss" 単一エントリでpostcss設定不要。CSS変数内蔵、Container Queriesネイティブ対応、oxcエンジンで10-40倍高速化、カラー関数簡略化。

1 分で読了
PWA オフラインアプリ実践:Service Worker + IndexedDB + Background Sync

PWA オフラインアプリ実践:Service Worker + IndexedDB + Background Sync

インストール可能・オフラインファーストなPWAを段階的構築:Service Worker キャッシュ戦略(Cache First / Stale-While-Revalidate)、構造化ローカルデータ用IndexedDBラッパー、ネットワーク復旧時にオフライン操作を自動リプレイするBackground Syncキュー。完全TypeScriptコード付き。

1 分で読了
CSS Container Queries 完全ガイド

CSS Container Queries 完全ガイド

モダンCSSの2大レスポンシブ機能:親コンテナ寸法に応じるContainer Queries、子孫要素の状態で親をスタイルする:has()セレクタ。

1 分で読了
Astro 静的サイト:アイランドアーキテクチャ & コンテンツコレクション

Astro 静的サイト:アイランドアーキテクチャ & コンテンツコレクション

Astroの革新:最小JSのアイランドアーキテクチャ、型安全なMarkdown/MDXのコンテンツコレクション、ゼロJSの高速静的サイト構築。

1 分で読了
フロントエンド性能:Core Web Vitals 実践

フロントエンド性能:Core Web Vitals 実践

Google検索順位のCore Web Vitals完全実践:LCP最適化(事前接続・画像優先度・CDN)、CLS除去(画像寸法・フォント・プレースホルダ)、INP改善(長タスク分割・デバウンス)。

1 分で読了