Composables 总览
Composables 是 Vue 3 组合式 API 的核心概念,用于封装和复用有状态的逻辑。
什么是 Composables?
Composables 是利用 Vue 3 组合式 API 来封装和复用有状态逻辑的函数。
特点:
- 以
use开头命名 - 返回响应式的状态和方法
- 可以在组件的
setup()中使用 - 支持组合和嵌套使用
Composables 分类
AI 相关
| Composable | 说明 | 文档 |
|---|---|---|
| useAiAction | AI 操作助手 | 查看 |
表格相关
| Composable | 说明 | 文档 |
|---|---|---|
| useTableSelection | 表格选择管理 | 查看 |
实时通信相关
| Composable | 说明 | 文档 |
|---|---|---|
| useStomp | STOMP 协议 WebSocket | 查看 |
| useDictSync | 字典数据同步 | 查看 |
| useOnlineCount | 在线人数统计 | 查看 |
布局相关
布局相关的 Composables 位于 src/layouts/useLayout.ts:
| Composable | 说明 |
|---|---|
| useLayout | 布局管理(侧边栏、设备检测等) |
使用方式
基本用法
vue
<script setup lang="ts">
import { useStomp, useDictSync, useOnlineCount } from '@/composables'
// WebSocket 连接
const { connect, disconnect } = useStomp()
// 字典同步
const { startSync, stopSync } = useDictSync()
// 在线人数
const { onlineCount } = useOnlineCount()
onMounted(() => {
connect()
startSync()
})
onUnmounted(() => {
disconnect()
stopSync()
})
</script>组合使用
vue
<script setup lang="ts">
import { useTableSelection } from '@/composables'
const {
selectedIds,
selectedRows,
handleSelectionChange,
clearSelection
} = useTableSelection()
// 表格选择变化时
const onSelectionChange = (rows: any[]) => {
handleSelectionChange(rows)
}
// 批量删除
const handleBatchDelete = () => {
if (selectedIds.value.length === 0) {
ElMessage.warning('请选择要删除的数据')
return
}
// 执行删除...
}
</script>创建自定义 Composable
基本结构
typescript
// composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
// 响应式状态
const count = ref(initialValue)
// 计算属性
const double = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
function decrement() {
count.value--
}
// 返回状态和方法
return {
count,
double,
increment,
decrement
}
}带副作用的 Composable
typescript
// composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(
target: EventTarget,
event: string,
callback: EventListener
) {
onMounted(() => {
target.addEventListener(event, callback)
})
onUnmounted(() => {
target.removeEventListener(event, callback)
})
}异步 Composable
typescript
// composables/useFetch.ts
import { ref } from 'vue'
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(false)
async function fetch() {
loading.value = true
error.value = null
try {
const response = await axios.get(url)
data.value = response.data
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
return {
data,
error,
loading,
fetch
}
}项目 Composables 导出
项目通过 src/composables/index.ts 统一导出所有 Composables:
typescript
// WebSocket 服务
export { setupWebSocket, cleanupWebSocket } from "./websocket";
export { useStomp, useDictSync, useOnlineCount } from "./websocket";
export type { DictMessage, DictChangeMessage, DictChangeCallback } from "./websocket";
// AI 相关
export { useAiAction } from "./ai/useAiAction";
export type { UseAiActionOptions, AiActionHandler } from "./ai/useAiAction";
// 表格相关
export { useTableSelection } from "./table/useTableSelection";最佳实践
1. 命名规范
- 使用
use前缀 - 使用驼峰命名
- 名称清晰表达功能
typescript
// ✅ 好的命名
useTableSelection
useDictSync
useOnlineCount
// ❌ 不好的命名
tableSelection
dictSync
getOnlineCount2. 单一职责
每个 Composable 只负责一个功能:
typescript
// ✅ 好 - 单一职责
function useTableSelection() { /* 只处理表格选择 */ }
function useTablePagination() { /* 只处理分页 */ }
// ❌ 不好 - 职责过多
function useTable() { /* 选择、分页、排序、筛选... */ }3. 类型定义
提供完整的 TypeScript 类型:
typescript
interface UseCounterOptions {
min?: number
max?: number
}
interface UseCounterReturn {
count: Ref<number>
increment: () => void
decrement: () => void
}
export function useCounter(
initialValue = 0,
options?: UseCounterOptions
): UseCounterReturn {
// ...
}4. 副作用清理
确保清理事件监听等副作用:
typescript
export function useEventListener(target, event, callback) {
onMounted(() => {
target.addEventListener(event, callback)
})
// 清理副作用
onUnmounted(() => {
target.removeEventListener(event, callback)
})
}5. 参数灵活性
支持多种参数形式:
typescript
// 支持 ref 和普通值
export function useTitle(title: MaybeRef<string>) {
const titleRef = ref(title)
watch(titleRef, (newTitle) => {
document.title = newTitle
}, { immediate: true })
return titleRef
}
// 使用
useTitle('页面标题')
useTitle(ref('响应式标题'))