Appearance
第8章:Composables 与组合式逻辑复用
1. 什么是 Composables?(与 Vue 组合式 API 的区别与联系)
1.1 概念
Composables 是 Nuxt.js 中的一个核心概念,它是基于 Vue 3 的组合式 API 构建的可复用逻辑块。Composables 允许你将相关的逻辑和状态封装到一个函数中,然后在多个组件中复用这些逻辑。
1.2 与 Vue 组合式 API 的区别与联系
联系:
- Composables 基于 Vue 3 的组合式 API
- 都使用
ref、computed、watch等 Vue 核心 API - 都用于组织和复用逻辑
区别:
- 目录结构:Nuxt 中的 Composables 存放在
composables目录中,会被自动导入 - 自动导入:Nuxt 中的 Composables 会被自动导入,无需手动导入
- 服务端兼容:Nuxt 中的 Composables 考虑了服务端渲染的兼容性
- 内置 Composables:Nuxt 提供了一些内置的 Composables,如
useNuxtApp、useRuntimeConfig等
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.ts、useApi.ts。
函数名:与文件名保持一致,使用 useXxx 格式,如 useCounter、useApi。
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→ 自动导入为useAuthcomposables/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。
