第6章:组合式API
第17节:组合式函数(Composables)
6.17.1 自定义组合式函数创建
组合式函数(Composables)是Vue 3中用于复用逻辑的重要特性,它允许我们将组件中的响应式逻辑提取到可复用的函数中。
基本概念
组合式函数是一个函数,它:
- 接收响应式数据作为输入
- 创建和管理自己的响应式状态
- 返回响应式数据和方法
- 可以使用其他组合式函数
示例:鼠标位置跟踪
// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
// 定义响应式状态
const x = ref(0)
const y = ref(0)
// 定义事件处理函数
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 生命周期钩子
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
// 返回响应式状态和方法
return { x, y }
}在组件中使用:
<template>
<div>
<h3>鼠标位置跟踪</h3>
<p>X: {{ x }}</p>
<p>Y: {{ y }}</p>
</div>
</template>
<script setup>
import { useMouse } from './useMouse'
// 使用组合式函数
const { x, y } = useMouse()
</script>示例:计数器逻辑
// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
// 定义响应式状态
const count = ref(initialValue)
// 定义计算属性
const doubleCount = computed(() => count.value * 2)
// 定义方法
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
// 返回响应式状态和方法
return {
count,
doubleCount,
increment,
decrement,
reset
}
}在组件中使用:
<template>
<div>
<h3>计数器</h3>
<p>count: {{ count }}</p>
<p>doubleCount: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './useCounter'
// 使用组合式函数,初始值为10
const { count, doubleCount, increment, decrement, reset } = useCounter(10)
</script>6.17.2 常用组合式函数示例
组合式函数可以用于各种场景,下面是一些常用的组合式函数示例:
useFetch:数据获取
// useFetch.js
import { ref, onMounted, watch } from 'vue'
export function useFetch(url) {
// 定义响应式状态
const data = ref(null)
const error = ref(null)
const loading = ref(false)
// 定义数据获取函数
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error('请求失败')
}
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 监听url变化,重新获取数据
watch(url, (newUrl) => {
if (newUrl) {
fetchData()
}
})
// 组件挂载时获取数据
onMounted(() => {
if (url.value) {
fetchData()
}
})
// 返回响应式状态和方法
return {
data,
error,
loading,
refetch: fetchData
}
}在组件中使用:
<template>
<div>
<h3>数据获取示例</h3>
<input v-model="url" placeholder="输入API地址" />
<button @click="refetch">重新获取</button>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误: {{ error }}</div>
<div v-else-if="data">
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useFetch } from './useFetch'
const url = ref('https://jsonplaceholder.typicode.com/todos/1')
const { data, error, loading, refetch } = useFetch(url)
</script>useLocalStorage:本地存储
// useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, initialValue) {
// 从本地存储获取初始值
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : initialValue)
// 监听value变化,同步到本地存储
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
// 返回响应式状态
return value
}在组件中使用:
<template>
<div>
<h3>本地存储示例</h3>
<input v-model="name" placeholder="输入姓名" />
<p>姓名: {{ name }}</p>
</div>
</template>
<script setup>
import { useLocalStorage } from './useLocalStorage'
// 使用本地存储,键名为'userName',初始值为空字符串
const name = useLocalStorage('userName', '')
</script>useDebounce:防抖
// useDebounce.js
import { ref, watch } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value)
let timer = null
watch(value, (newValue) => {
// 清除之前的定时器
if (timer) {
clearTimeout(timer)
}
// 设置新的定时器
timer = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
})
return debouncedValue
}在组件中使用:
<template>
<div>
<h3>防抖示例</h3>
<input v-model="inputValue" placeholder="输入搜索内容" />
<p>防抖后的值: {{ debouncedValue }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useDebounce } from './useDebounce'
const inputValue = ref('')
// 使用防抖,延迟500ms
const debouncedValue = useDebounce(inputValue, 500)
</script>useThrottle:节流
// useThrottle.js
import { ref, watch } from 'vue'
export function useThrottle(value, delay = 300) {
const throttledValue = ref(value.value)
let lastUpdateTime = 0
watch(value, (newValue) => {
const now = Date.now()
if (now - lastUpdateTime >= delay) {
lastUpdateTime = now
throttledValue.value = newValue
}
})
return throttledValue
}6.17.3 组合式函数的最佳实践
命名约定
- 使用
use前缀命名组合式函数,如useMouse、useFetch - 函数名应清晰反映其功能
- 内部变量名应遵循驼峰命名法
响应式状态管理
- 使用
ref和reactive创建响应式状态 - 避免在组合式函数中直接修改外部状态
- 对于复杂状态,考虑使用
reactive进行组织
生命周期管理
- 在组合式函数内部管理自己的生命周期
- 使用
onMounted、onUnmounted等钩子清理资源 - 避免在组合式函数中依赖外部组件的生命周期
依赖注入
- 对于需要全局状态的组合式函数,可以使用
provide和inject - 考虑使用
readonly包装注入的数据,防止意外修改
类型安全
- 在TypeScript中,为组合式函数添加类型定义
- 使用泛型支持不同类型的数据
- 为返回值添加明确的类型
示例:带类型的useFetch
// useFetch.ts
import { ref, onMounted, watch, Ref } from 'vue'
export function useFetch<T>(url: Ref<string | null>) {
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const loading = ref(false)
const fetchData = async () => {
if (!url.value) return
loading.value = true
error.value = null
try {
const response = await fetch(url.value)
if (!response.ok) {
throw new Error('请求失败')
}
data.value = await response.json()
} catch (err) {
error.value = err instanceof Error ? err.message : '未知错误'
} finally {
loading.value = false
}
}
watch(url, (newUrl) => {
if (newUrl) {
fetchData()
}
})
onMounted(() => {
if (url.value) {
fetchData()
}
})
return {
data,
error,
loading,
refetch: fetchData
}
}6.17.4 组合式函数与Mixin对比
Mixin的问题
- 命名冲突:不同Mixin可能定义相同名称的变量或方法,导致冲突
- 来源不明确:组件中使用的变量和方法可能来自多个Mixin,难以追踪来源
- 依赖关系不清晰:Mixin之间的依赖关系不明确,容易产生副作用
- 代码复用不灵活:Mixin是静态组合,无法根据条件动态使用
组合式函数的优势
- 明确的来源:通过解构赋值,变量和方法的来源清晰
- 无命名冲突:可以重命名变量和方法,避免冲突
- 灵活的组合:可以根据条件动态使用,支持嵌套组合
- 类型安全:在TypeScript中具有更好的类型支持
- 更好的可维护性:代码逻辑封装在函数内部,便于测试和维护
对比示例
使用Mixin:
// counterMixin.js
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
// 使用Mixin
new Vue({
mixins: [counterMixin],
mounted() {
this.increment() // 来源不明确
console.log(this.count) // 来源不明确
}
})使用组合式函数:
// useCounter.js
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// 使用组合式函数
const { count, increment } = useCounter() // 来源明确
increment()
console.log(count.value)总结
组合式函数是Vue 3中用于复用逻辑的重要特性,它具有以下优点:
- 更好的代码组织:将相关逻辑封装在一个函数中,便于维护和测试
- 明确的依赖关系:通过参数和返回值,明确函数之间的依赖关系
- 灵活的组合:可以根据需要组合多个组合式函数
- 类型安全:在TypeScript中具有更好的类型支持
- 无命名冲突:通过解构赋值,避免命名冲突
- 更好的性能:只包含必要的逻辑,没有额外的开销
通过学习和使用组合式函数,你可以编写出更加模块化、可复用和可维护的Vue代码。组合式函数是Vue 3中推荐的逻辑复用方式,它解决了Mixin存在的诸多问题,提供了更好的开发体验。
在下一章中,我们将学习Vue Router,这是Vue官方的路由管理库,用于实现单页面应用的路由功能。