第 1 节 概述
前一篇讲的 Pinia 解决了"数据共享"的问题。
但还有一类重复:逻辑的重复 。
比如:多个组件都需要追踪鼠标位置,或者都需要用 fetch 调用 API。每个组件里写一遍同样的逻辑,很啰嗦。
Vue 3 提供了组合式函数(Composable) 来复用逻辑。它其实就是函数,只不过内部用到了 Vue 的响应式 API。
命名约定:以 use 开头,比如 useMouse、useFetch。
第 2 节 第一个 Composable
追踪鼠标位置的逻辑,封装成 useMouse:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/composables/useMouse.ts
import { ref , onMounted , onUnmounted } from 'vue'
export function useMouse() {
const x = ref ( 0 )
const y = ref ( 0 )
function update ( event : MouseEvent ) {
x . value = event . clientX
y . value = event . clientY
}
onMounted (() => {
window . addEventListener ( 'mousemove' , update )
})
onUnmounted (() => {
window . removeEventListener ( 'mousemove' , update )
})
return { x , y }
}
在组件中使用:
1
2
3
4
5
6
7
8
9
< script setup lang = "ts" >
import { useMouse } from '@/composables/useMouse'
const { x , y } = useMouse ()
</ script >
< template >
< p > 鼠标位置 : {{ x }}, {{ y }}</ p >
</ template >
这就是 composable 的核心思路:把组件中用到的响应式数据和生命周期逻辑,提取到一个函数里 。
任何需要鼠标位置的组件,都可以直接引用 useMouse,不用重新写一遍事件监听。
第 3 节 带参数的 Composable
封装一个通用的 API 请求 composable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/composables/useFetch.ts
import { ref , watchEffect } from 'vue'
export function useFetch ( url : string ) {
const data = ref ( null )
const error = ref ( null )
const loading = ref ( true )
watchEffect ( async () => {
loading . value = true
error . value = null
try {
const response = await fetch ( url )
data . value = await response . json ()
} catch ( e ) {
error . value = e as Error
} finally {
loading . value = false
}
})
return { data , error , loading }
}
在组件中使用:
1
2
3
4
5
6
7
8
9
10
11
12
< script setup lang = "ts" >
import { useFetch } from '@/composables/useFetch'
const { data , loading } = useFetch ( '/api/articles' )
</ script >
< template >
< p v-if = "loading" > 加载中... </ p >
< div v-else v-for = "item in data" :key="item.id" >
{{ item.title }}
</ div >
</ template >
调用 useFetch 时传入 URL,返回的 data 是响应式的(用了 ref),页面会随数据加载自动更新。
第 4 节 Composable vs Pinia
它们容易混淆,但用途不同:
对比 Composable Pinia 本质 函数,复用逻辑 store,管理共享状态 数据范围 只在调用它的组件中有效 所有组件共享同一份 使用方式 每个组件各自调用 所有组件用同一个 useXxxStore() 典型场景 鼠标追踪、API 请求、表单逻辑 用户信息、购物车、全局配置
举个例子:
useMouse() → 每个组件调一次,得到自己的鼠标位置(互不干扰)useUserStore() → 所有组件调,拿到同一个用户对象(保持一致) 技巧
如果一段逻辑只涉及计算和副作用,不涉及跨组件共享,写成 composable。如果需要跨组件共享同一份状态,用 Pinia。
第 5 节 Composable 的复用规则
组合式函数之间可以互相调用。这比 mixin(Vue 2 的旧方案)灵活得多:
1
2
3
4
5
6
export function useUser() {
const { data : user , loading } = useFetch ( '/api/user' )
const { x , y } = useMouse () // composable 内部调用另一个 composable
return { user , loading , mouseX : x , mouseY : y }
}
这与 Vue 2 的 mixin 相比有几个优势:
对比 mixin composable 来源 多个 mixin 属性冲突时不知道来自哪 每个变量从函数解构,来源明确 命名冲突 多个 mixin 可能有同名属性 拆解时可以用 : 重命名 参数化 不支持动态参数 可以接收参数动态调整行为