Composable — 封装可复用逻辑
博客里"请求文章列表 + 加载状态 + 错误提示"这套逻辑,首页要用、分类页要用、搜索页也要用。若每个组件复制一遍,改一处就得改多处。Vue3 组合式 API 的答案是 Composable(组合式函数):把可复用逻辑封装成普通函数,谁需要谁调用。本节以 usePosts 为例讲清设计与使用。
什么是 Composable
组合式函数是普通函数,但内部可以使用 ref、computed、watch、生命周期等组合式 API,并返回需要暴露的状态与方法。约定俗成:函数名以 use 开头,如 usePosts、useDebounce,一看名字就知道用途。
第一个 Composable:usePosts
把"请求文章列表"的全部逻辑搬进 composables/usePosts.js:
// composables/usePosts.js
import { ref, onMounted } from 'vue'
import axios from 'axios'
export function usePosts() {
const posts = ref([]) // 文章列表
const loading = ref(true) // 是否加载中
const error = ref('') // 错误信息
async function fetchPosts() {
loading.value = true
error.value = ''
try {
const { data } = await axios.get('/api/posts')
posts.value = data
} catch (e) {
error.value = '文章加载失败'
} finally {
loading.value = false
}
}
onMounted(fetchPosts) // 使用时自动请求一次
return { posts, loading, error, fetchPosts } // 暴露给组件
}
在组件中使用 usePosts
组件里只需几行,就拥有完整的状态与刷新方法:
<!-- HomeView.vue -->
<script setup>
import { usePosts } from '../composables/usePosts'
const { posts, loading, error, fetchPosts } = usePosts() // 拿状态
</script>
<template>
<p v-if="loading">加载中...</p>
<p v-else-if="error">{{ error }}</p>
<ul v-else>
<li v-for="p in posts" :key="p.id">{{ p.title }}</li>
</ul>
<button @click="fetchPosts">刷新列表</button>
</template>
运行后自动加载文章列表,点"刷新列表"可再次请求,逻辑只维护在一处。
带参数的 Composable:useSearchPosts
设计成接收参数,函数就能适配更多场景,搜索逻辑也可以封装:
// composables/useSearchPosts.js
import { ref, watch } from 'vue'
import axios from 'axios'
export function useSearchPosts() {
const keyword = ref('') // 组件用 v-model 绑定
const results = ref([])
watch(keyword, async (kw) => { // 关键词变化就请求接口
if (!kw) { results.value = []; return }
const { data } = await axios.get('/api/search', { params: { q: kw } })
results.value = data
})
return { keyword, results }
}
组件绑定输入框并展示 results 即可,防抖等细节可再封装成 useDebounce。
组合使用与对比 mixins
Composable 能互相调用、层层组合,把复杂逻辑拆成可单独测试的小积木,旧方案 mixins 则会悄悄合并属性、命名冲突难排查。Composable 显式解构、来源清楚、类型推导友好,是 Vue3 推荐做法。
小结:Composable 把带状态的逻辑封装成 useXxx 函数,组件按需调用即可复用,还能相互组合。下一节用 Pinia 管理"收藏文章"这类全局共享数据。