404 Not Found

404 Not Found


nginx

Composables: Composable Functions

Charlie's team has a lot of duplicate logic—price formatting is written separately in 10 components, and search logic is scattered across 3 pages. Alice's favorites feature needs to share state across pages. Bob needs a generic pagination logic. Composables are the solution—extract the logic into reusable functions.

1. What You'll Learn


2. A True Story of an Architect

(1) Pain Point: Duplicate logic scattered throughout the code

MegaShop has 10 components that need to format prices as "USD 2,999.99," and each one requires a custom Intl.NumberFormat implementation. Alice has implemented the "Favorites" feature on both the home page and the product detail page, but the logic is inconsistent—items favorited on the home page do not appear on the product detail page.

(2) The Composable Solution

Once extracted into a Composable, define it once and reuse it everywhere:

TYPESCRIPT
// composables/usePriceFormat.ts
export function usePriceFormat(price: Ref<number>, currency = 'USD') {
  return computed(() => new Intl.NumberFormat('en-US', {
    style: 'currency', currency
  }).format(price.value))
}

(3) Benefits: DRY + Consistency

Ten components share the same price formatting logic, and the wishlist status remains consistent across all pages thanks to useWishlist. Code volume was reduced by 60%, and bugs were reduced by 80%.


3. Overview of Built-in Composables

(1) Built-in Composable Dependency Graph

100%
graph TB
    A[Nuxt Built-in Composables] --> B[Data Fetching]
    A --> C[State Management]
    A --> D[Navigation]
    A --> E[Head Management]
    A --> F[Context]
    A --> G[Utility]

    B --> B1[useFetch]
    B --> B2[useAsyncData]
    B --> B3[$fetch]
    C --> C1[useState]
    C --> C2[useCookie]
    D --> D1[useRouter]
    D --> D2[useRoute]
    D --> D3[navigateTo]
    E --> E1[useHead]
    E --> E2[useSeoMeta]
    F --> F1[useRequestHeaders]
    F --> F2[useRuntimeConfig]
    G --> G1[useAppConfig]
    G --> G2[useHydration]

(2) Quick Reference for Core Built-in Composables

Composable Purpose SSR Supported Return Value
useFetch Data Retrieval { data, pending, error, refresh }
useAsyncData General Async { data, pending, error, refresh }
useState Shared state Ref
useCookie Cookie Operations Ref
useRouter Router Instance ⚠️ Client-side only Router
useRoute Current Route Route
useHead Head Management void
useSeoMeta SEO Metadata void
useRuntimeConfig Runtime Configuration RuntimeConfig
useRequestHeaders Request Headers ✅ Server-side only Headers

(1) ▶ Example: Sharing State Across Components with useState

VUE
<script setup lang="ts">
// Share notification state across components
const notifications = useState<Notification[]>('notifications', () => [])

function addNotification(message: string, type: 'success' | 'error' = 'success') {
  notifications.value.push({ id: Date.now(), message, type })
  setTimeout(() => {
    notifications.value = notifications.value.filter(n => n.id !== Date.now())
  }, 3000)
}
</script>

Output:

TEXT
// Execution Successful

4. Custom Composable

(1) Naming and Directory Conventions

Rule Description Example
Table of Contents composables/ composables/useCart.ts
Name use prefix usePriceFormat
Export Named Export export function usePriceFormat()
Type Interface defined in the same file interface PriceOptions {}
Auto-import ✅ Automatic No manual import required

(1) ▶ Example: usePriceFormat Price Formatting

TYPESCRIPT
// composables/usePriceFormat.ts
interface PriceFormatOptions {
  currency?: string
  locale?: string
  showDecimals?: boolean
}

export function usePriceFormat(
  price: Ref<number> | number,
  options: PriceFormatOptions = {}
) {
  const { currency = 'USD', locale = 'en-US', showDecimals = true } = options

  const formatted = computed(() => {
    const value = unref(price)
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency,
      minimumFractionDigits: showDecimals ? 2 : 0,
      maximumFractionDigits: showDecimals ? 2 : 0
    }).format(value)
  })

  return { formatted }
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: useWishlist Favorites

TYPESCRIPT
// composables/useWishlist.ts
export function useWishlist() {
  const wishlist = useState<number[]>('wishlist', () => [])

  function toggle(productId: number) {
    const index = wishlist.value.indexOf(productId)
    if (index === -1) {
      wishlist.value.push(productId)
    } else {
      wishlist.value.splice(index, 1)
    }
  }

  function isWishlisted(productId: number): boolean {
    return wishlist.value.includes(productId)
  }

  const count = computed(() => wishlist.value.length)

  return { wishlist, toggle, isWishlisted, count }
}

Output:

TEXT
// Execution Successful

(3) ▶ Example: useProductSearch Search Logic

TYPESCRIPT
// composables/useProductSearch.ts
interface SearchParams {
  query?: string
  category?: string
  minPrice?: number
  maxPrice?: number
  sortBy?: 'price' | 'name' | 'rating'
  page?: number
}

export function useProductSearch(initialParams: SearchParams = {}) {
  const params = reactive<SearchParams>({
    query: '',
    category: '',
    minPrice: 0,
    maxPrice: 10000,
    sortBy: 'rating',
    page: 1,
    ...initialParams
  })

  const { data, pending, error, refresh } = useFetch('/api/products/search', {
    query: params,
    watch: [params],
    default: () => ({ items: [], total: 0 })
  })

  function resetFilters() {
    params.query = ''
    params.category = ''
    params.minPrice = 0
    params.maxPrice = 10000
    params.sortBy = 'rating'
    params.page = 1
  }

  return { params, data, pending, error, refresh, resetFilters }
}

Output:

TEXT
// Execution Successful

5. The Composable Design Pattern

(1) Comparison of Design Patterns

Pattern Characteristics Use Cases Examples
Sync Composable Return computed/ref Formatting/Computation usePriceFormat
Asynchronous Composable Internal call to useFetch Data retrieval useProductSearch
Parameter Responsiveness Automatic Refresh via watch Filter/Search useProductSearch
State Sharing Inside useState Cross-Component State useWishlist
Composable Calling Other Composables Complex Logic useCheckout

(1) ▶ Example: Composable—useCheckout

TYPESCRIPT
// composables/useCheckout.ts
export function useCheckout() {
  // Compose other composables
  const cartStore = useCartStore()
  const { formatted: total } = usePriceFormat(
    computed(() => cartStore.totalPrice)
  )
  const { isAuthenticated, user } = storeToRefs(useUserStore())

  const isProcessing = ref(false)
  const orderId = ref<string | null>(null)

  async function processCheckout() {
    if (!isAuthenticated.value) {
      navigateTo('/login')
      return
    }

    isProcessing.value = true
    try {
      const order = await $fetch('/api/orders', {
        method: 'POST',
        body: {
          items: cartStore.items,
          userId: user.value?.id,
          total: cartStore.totalPrice
        }
      })
      orderId.value = order.id
      cartStore.clearCart()
      navigateTo(`/orders/${order.id}`)
    } catch (err) {
      console.error('Checkout failed:', err)
    } finally {
      isProcessing.value = false
    }
  }

  return { total, isProcessing, orderId, processCheckout, isAuthenticated }
}

Output:

TEXT
// Execution Successful

(2) Composable Design Principle

Principle Explanation Counterexample
Single Responsibility One Composable does one thing useShopAndCart()
Parameter is responsive Accepts Ref parameters Accepts only raw values
Return Value Destructuring Return Named Object Return Array
Clear Naming use + verb/noun doStuff()
No side effects Does not directly modify the DOM Operates on the DOM internally

6. Comprehensive Example: The MegaShop Composable Architecture

TYPESCRIPT
// composables/usePagination.ts
export function usePagination(totalItems: Ref<number>, pageSize = 20) {
  const route = useRoute()
  const router = useRouter()

  const currentPage = computed(() => Number(route.query.page) || 1)
  const totalPages = computed(() => Math.ceil(totalItems.value / pageSize))
  const hasNext = computed(() => currentPage.value < totalPages.value)
  const hasPrev = computed(() => currentPage.value > 1)

  function goToPage(page: number) {
    router.push({ query: { ...route.query, page: String(page) } })
  }

  function nextPage() {
    if (hasNext.value) goToPage(currentPage.value + 1)
  }

  function prevPage() {
    if (hasPrev.value) goToPage(currentPage.value - 1)
  }

  const offset = computed(() => (currentPage.value - 1) * pageSize)

  return { currentPage, totalPages, hasNext, hasPrev, offset, goToPage, nextPage, prevPage }
}
TYPESCRIPT
// composables/useNotification.ts
interface Notification { id: number; message: string; type: 'success' | 'error' | 'info' }

export function useNotification() {
  const notifications = useState<Notification[]>('notifications', () => [])

  function notify(message: string, type: Notification['type'] = 'success') {
    const id = Date.now()
    notifications.value = [...notifications.value, { id, message, type }]
    setTimeout(() => {
      notifications.value = notifications.value.filter(n => n.id !== id)
    }, 3000)
  }

  function dismiss(id: number) {
    notifications.value = notifications.value.filter(n => n.id !== id)
  }

  return { notifications, notify, dismiss }
}

❓ FAQ

Q How do I choose between Composables and Pinia Store?
A Use Store for persistent state across components (e.g., shopping cart, user data), and use Composables for reusing logic (e.g., formatting, search). Composables are lighter-weight, while Store offers DevTools support.
Q Can I use useFetch in a Composable?
A Yes, this is called an asynchronous Composable. You need to use await when calling it, just like you would in a page. useProductSearch is designed this way.
Q Why must Composables start with "use"?
A Nuxt 3's auto-import feature only scans files under the composables/ directory and does not require the "use" prefix. However, the Vue community convention is to start them with "use" for easier identification and tool support.
Q Can I use useState in Composable?
A Yes. The state created by useState is automatically hydrated during SSR and on the client side, making it suitable for sharing across components. useWishlist uses useState for storage.
Q Should the parameters of a Composable be Refs or raw values?
A We recommend accepting Refs so that the Composable can update reactively when the parameters change. Use unref() to support both types.
Q Do multiple components that call the same Composable share state?
A If useState is used inside the Composable, the state is shared. If ref or reactive is used, a separate state is created each time the Composable is called. The choice depends on your needs.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Create a usePriceFormat Composable to format prices as USD/JPY/CNY in different components.
  2. Advanced Exercise (Difficulty: ⭐⭐): Create useProductSearch to implement keyword search, category filtering, and pagination. Verify that the page automatically refreshes when the watch parameter changes.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a useCheckout composite Composable that integrates the shopping cart Store, price formatting, and order submission to implement a complete checkout process.

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏