Skip to content

第8章:Composables 与组合式逻辑复用

1. 什么是 Composables?(与 Vue 组合式 API 的区别与联系)

1.1 概念

Composables 是 Nuxt.js 中的一个核心概念,它是基于 Vue 3 的组合式 API 构建的可复用逻辑块。Composables 允许你将相关的逻辑和状态封装到一个函数中,然后在多个组件中复用这些逻辑。

1.2 与 Vue 组合式 API 的区别与联系

联系

  • Composables 基于 Vue 3 的组合式 API
  • 都使用 refcomputedwatch 等 Vue 核心 API
  • 都用于组织和复用逻辑

区别

  • 目录结构:Nuxt 中的 Composables 存放在 composables 目录中,会被自动导入
  • 自动导入:Nuxt 中的 Composables 会被自动导入,无需手动导入
  • 服务端兼容:Nuxt 中的 Composables 考虑了服务端渲染的兼容性
  • 内置 Composables:Nuxt 提供了一些内置的 Composables,如 useNuxtAppuseRuntimeConfig

2. 内置 Composables 使用(useNuxtApp、useRuntimeConfig 等)

2.1 useNuxtApp

作用:获取 Nuxt 应用实例,访问 Nuxt 运行时上下文。

基本用法

vue
<template>
  <div>
    <h1>Nuxt App Info</h1>
    <p>App version: {{ appVersion }}</p>
  </div>
</template>

<script setup>
const nuxtApp = useNuxtApp()
const appVersion = ref('1.0.0')

// 访问 Nuxt 运行时上下文
console.log(nuxtApp.$config)
console.log(nuxtApp.$router)
</script>

2.2 useRuntimeConfig

作用:访问运行时配置,包括公共配置和私有配置。

配置:在 nuxt.config.ts 中配置:

typescript
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiBase: '/api'
    },
    private: {
      apiKey: process.env.API_KEY
    }
  }
})

使用

vue
<template>
  <div>
    <h1>Runtime Config</h1>
    <p>API Base: {{ config.public.apiBase }}</p>
  </div>
</template>

<script setup>
const config = useRuntimeConfig()

// 访问公共配置
console.log(config.public.apiBase)

// 访问私有配置(仅在服务端可用)
if (process.server) {
  console.log(config.apiKey)
}
</script>

2.3 useRouter

作用:获取 Vue Router 实例,用于编程式导航。

基本用法

vue
<template>
  <div>
    <h1>Router Example</h1>
    <button @click="navigateToHome">Go Home</button>
    <button @click="navigateToAbout">Go to About</button>
  </div>
</template>

<script setup>
const router = useRouter()

function navigateToHome() {
  router.push('/')
}

function navigateToAbout() {
  router.push('/about')
}
</script>

2.4 useRoute

作用:获取当前路由信息,包括路由参数、查询参数等。

基本用法

vue
<template>
  <div>
    <h1>Route Info</h1>
    <p>Current path: {{ route.path }}</p>
    <p>Route params: {{ JSON.stringify(route.params) }}</p>
    <p>Query params: {{ JSON.stringify(route.query) }}</p>
  </div>
</template>

<script setup>
const route = useRoute()

// 监听路由变化
watch(
  () => route.params.id,
  (newId) => {
    // 处理路由参数变化
  }
)
</script>

2.5 useHead

作用:管理页面头部信息,如标题、meta 标签等。

基本用法

vue
<template>
  <div>
    <h1>Page Title</h1>
  </div>
</template>

<script setup>
useHead({
  title: 'Page Title - My Nuxt App',
  meta: [
    { name: 'description', content: 'This is a page description' },
    { name: 'keywords', content: 'nuxt, vue, javascript' }
  ],
  link: [
    { rel: 'canonical', href: 'https://example.com/page' }
  ]
})
</script>

2.6 useCookie

作用:管理浏览器 cookie,支持服务端渲染。

基本用法

vue
<template>
  <div>
    <h1>Welcome {{ username }}</h1>
    <input v-model="username" placeholder="Enter your name" />
  </div>
</template>

<script setup>
const username = useCookie('username', {
  default: 'Guest',
  maxAge: 60 * 60 * 24 * 7 // 7天
})
</script>

2.7 useState

作用:创建响应式状态,支持服务端渲染。

基本用法

vue
<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
const count = useState('count', () => 0)

function increment() {
  count.value++
}
</script>

3. 自定义 Composables 封装(复用逻辑,如请求封装、权限判断)

3.1 基础 Composables

创建 composables/useCounter.ts

typescript
// composables/useCounter.ts
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = initialValue
  }

  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset
  }
}

使用

vue
<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <p>Double count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
    <button @click="reset">Reset</button>
  </div>
</template>

<script setup>
// 无需导入,自动导入
const { count, doubleCount, increment, decrement, reset } = useCounter(10)
</script>

3.2 带参数的 Composables

创建 composables/useApi.ts

typescript
// composables/useApi.ts
import { $fetch, type FetchOptions } from 'ofetch'

const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'

const api = $fetch.create({
  baseURL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  },
  onRequest({ options }) {
    const token = useCookie('token').value
    if (token) {
      options.headers = {
        ...options.headers,
        Authorization: `Bearer ${token}`
      }
    }
  }
})

export function useApi() {
  return {
    get: <T>(url: string, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'get' })
    },
    post: <T>(url: string, body?: any, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'post', body })
    },
    put: <T>(url: string, body?: any, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'put', body })
    },
    delete: <T>(url: string, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'delete' })
    }
  }
}

使用

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { get } = useApi()
const { data: posts, pending, error } = useAsyncData('posts', () => {
  return get('/posts')
})
</script>

3.3 复杂 Composables

创建 composables/useAuth.ts

typescript
// composables/useAuth.ts
import { ref, computed } from 'vue'

export function useAuth() {
  const user = useState('user', () => null)
  const token = useCookie('token')

  const isAuthenticated = computed(() => !!token.value)

  async function login(email: string, password: string) {
    try {
      const { post } = useApi()
      const response = await post('/auth/login', { email, password })
      token.value = response.token
      user.value = response.user
      return response
    } catch (error) {
      throw error
    }
  }

  function logout() {
    token.value = null
    user.value = null
  }

  return {
    user,
    token,
    isAuthenticated,
    login,
    logout
  }
}

使用

vue
<template>
  <div>
    <h1>Auth Example</h1>
    <div v-if="isAuthenticated">
      <p>Welcome, {{ user?.name }}</p>
      <button @click="logout">Logout</button>
    </div>
    <div v-else>
      <form @submit.prevent="handleLogin">
        <div>
          <label for="email">Email</label>
          <input type="email" id="email" v-model="email" />
        </div>
        <div>
          <label for="password">Password</label>
          <input type="password" id="password" v-model="password" />
        </div>
        <button type="submit">Login</button>
      </form>
    </div>
  </div>
</template>

<script setup>
const { user, isAuthenticated, login, logout } = useAuth()
const email = ref('')
const password = ref('')

async function handleLogin() {
  try {
    await login(email.value, password.value)
  } catch (error) {
    console.error('Login failed:', error)
  }
}
</script>

4. Composables 目录规范与自动导入

4.1 目录结构

推荐目录结构

composables/
├── useApi.ts         # API 相关逻辑
├── useAuth.ts        # 认证相关逻辑
├── useCounter.ts     # 计数器逻辑
├── useLocalStorage.ts # 本地存储逻辑
└── index.ts          # 导出所有 Composables(可选)

4.2 命名规范

文件名:使用 useXxx.ts 命名格式,如 useCounter.tsuseApi.ts

函数名:与文件名保持一致,使用 useXxx 格式,如 useCounteruseApi

4.3 自动导入

特性:在 composables 目录下的 Composables 会被自动导入,无需手动导入。

使用:直接在组件中使用 Composables 函数。

vue
<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
// 无需导入,自动导入
const { count, increment } = useCounter()
</script>

4.4 嵌套目录

嵌套目录:Composables 可以放在 composables 目录的子目录中。

示例

  • composables/auth/useAuth.ts → 自动导入为 useAuth
  • composables/api/useApi.ts → 自动导入为 useApi

使用:直接使用函数名,不包含目录前缀。

vue
<template>
  <div>
    <h1>Auth Example</h1>
    <!-- 内容 -->
  </div>
</template>

<script setup>
// 无需导入,自动导入
const { user, login, logout } = useAuth()
</script>

小结

本章介绍了 Nuxt.js 中的 Composables 概念、内置 Composables 的使用、自定义 Composables 的封装以及 Composables 目录规范与自动导入。通过本章的学习,你应该已经掌握了如何创建和使用 Composables,以及如何利用 Composables 来复用逻辑,提高代码的可维护性和可复用性。

在接下来的章节中,我们将学习 Nuxt.js 的服务端渲染与静态站点生成、Nuxt 生态集成等核心特性,帮助你更深入地理解和使用 Nuxt.js。

© 2026 编程马·菜鸟教程 版权所有