Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Vue3组合式函数教程,详解Composables概念、开发模式、最佳实践。包含完整实战案例,适合Vue3开发者掌握现代代码复用技术。
核心关键词:Vue3组合式函数、Composables、Vue3代码复用、组合式API、前端函数式编程
长尾关键词:Vue3组合式函数怎么写、Composables最佳实践、Vue3代码复用方案、组合式函数vs混入、Vue3函数式编程
通过本节组合式函数(Composables),你将系统性掌握:
什么是组合式函数?组合式函数(Composables)是利用Vue3 Composition API的响应式特性来封装和复用有状态逻辑的函数。它是Vue3推荐的代码复用方案,解决了混入的诸多问题。
💡 设计理念:组合式函数体现了"组合优于继承"的设计原则,通过函数组合来实现代码复用,提供了更加灵活和可维护的解决方案。
让我们从最简单的组合式函数开始:
// 🎉 基础组合式函数示例
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
// 响应式状态
const count = ref(initialValue)
// 计算属性
const doubleCount = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
// 方法
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
const setCount = (value) => {
count.value = value
}
// 返回需要暴露的状态和方法
return {
// 状态
count: readonly(count), // 只读,防止外部直接修改
doubleCount,
isEven,
// 方法
increment,
decrement,
reset,
setCount
}
}<!-- 在组件中使用组合式函数 -->
<template>
<div>
<h3>计数器示例</h3>
<p>当前计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<p>是否为偶数: {{ isEven ? '是' : '否' }}</p>
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">重置</button>
<button @click="setCount(10)">设为10</button>
</div>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter'
// 使用组合式函数
const {
count,
doubleCount,
isEven,
increment,
decrement,
reset,
setCount
} = useCounter(5) // 初始值为5
</script>// 🎉 实际应用案例:用户管理
// composables/useUsers.js
import { ref, computed, watch } from 'vue'
import { userAPI } from '@/api/user'
export function useUsers() {
// 状态管理
const users = ref([])
const loading = ref(false)
const error = ref(null)
const searchQuery = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 计算属性
const filteredUsers = computed(() => {
if (!searchQuery.value) return users.value
return users.value.filter(user =>
user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const totalPages = computed(() => {
return Math.ceil(total.value / pageSize.value)
})
const hasUsers = computed(() => users.value.length > 0)
const hasError = computed(() => error.value !== null)
// 方法
const fetchUsers = async (page = 1, size = 10) => {
loading.value = true
error.value = null
try {
const response = await userAPI.getUsers({
page,
size,
search: searchQuery.value
})
users.value = response.data
total.value = response.total
currentPage.value = page
} catch (err) {
error.value = err.message
users.value = []
} finally {
loading.value = false
}
}
const createUser = async (userData) => {
loading.value = true
error.value = null
try {
const newUser = await userAPI.createUser(userData)
users.value.push(newUser)
total.value++
return newUser
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const updateUser = async (userId, userData) => {
loading.value = true
error.value = null
try {
const updatedUser = await userAPI.updateUser(userId, userData)
const index = users.value.findIndex(user => user.id === userId)
if (index !== -1) {
users.value[index] = updatedUser
}
return updatedUser
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const deleteUser = async (userId) => {
loading.value = true
error.value = null
try {
await userAPI.deleteUser(userId)
users.value = users.value.filter(user => user.id !== userId)
total.value--
// 如果当前页没有数据了,回到上一页
if (users.value.length === 0 && currentPage.value > 1) {
currentPage.value--
await fetchUsers(currentPage.value, pageSize.value)
}
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const searchUsers = (query) => {
searchQuery.value = query
currentPage.value = 1
fetchUsers(1, pageSize.value)
}
const changePage = (page) => {
if (page >= 1 && page <= totalPages.value) {
fetchUsers(page, pageSize.value)
}
}
const changePageSize = (size) => {
pageSize.value = size
currentPage.value = 1
fetchUsers(1, size)
}
const refreshUsers = () => {
fetchUsers(currentPage.value, pageSize.value)
}
const clearError = () => {
error.value = null
}
// 监听搜索查询变化
watch(searchQuery, (newQuery) => {
if (newQuery === '') {
// 清空搜索时重新获取数据
fetchUsers(1, pageSize.value)
}
})
// 返回API
return {
// 状态
users: readonly(users),
loading: readonly(loading),
error: readonly(error),
searchQuery,
currentPage: readonly(currentPage),
pageSize: readonly(pageSize),
total: readonly(total),
// 计算属性
filteredUsers,
totalPages,
hasUsers,
hasError,
// 方法
fetchUsers,
createUser,
updateUser,
deleteUser,
searchUsers,
changePage,
changePageSize,
refreshUsers,
clearError
}
}<!-- 使用用户管理组合式函数的组件 -->
<template>
<div class="user-management">
<div class="header">
<h2>用户管理</h2>
<div class="actions">
<input
v-model="searchQuery"
placeholder="搜索用户..."
class="search-input"
>
<button @click="showCreateModal = true" class="btn-primary">
新增用户
</button>
</div>
</div>
<!-- 错误提示 -->
<div v-if="hasError" class="error-message">
{{ error }}
<button @click="clearError">×</button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading">
加载中...
</div>
<!-- 用户列表 -->
<div v-else-if="hasUsers" class="user-list">
<div
v-for="user in filteredUsers"
:key="user.id"
class="user-item"
>
<div class="user-info">
<h4>{{ user.name }}</h4>
<p>{{ user.email }}</p>
</div>
<div class="user-actions">
<button @click="editUser(user)" class="btn-secondary">
编辑
</button>
<button @click="confirmDelete(user)" class="btn-danger">
删除
</button>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="empty-state">
<p>暂无用户数据</p>
<button @click="refreshUsers" class="btn-primary">
刷新
</button>
</div>
<!-- 分页 -->
<div v-if="totalPages > 1" class="pagination">
<button
@click="changePage(currentPage - 1)"
:disabled="currentPage === 1"
>
上一页
</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button
@click="changePage(currentPage + 1)"
:disabled="currentPage === totalPages"
>
下一页
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useUsers } from '@/composables/useUsers'
// 使用组合式函数
const {
users,
loading,
error,
searchQuery,
currentPage,
totalPages,
hasUsers,
hasError,
filteredUsers,
fetchUsers,
createUser,
updateUser,
deleteUser,
changePage,
refreshUsers,
clearError
} = useUsers()
// 组件本地状态
const showCreateModal = ref(false)
const showEditModal = ref(false)
const editingUser = ref(null)
// 组件方法
const editUser = (user) => {
editingUser.value = { ...user }
showEditModal.value = true
}
const confirmDelete = async (user) => {
if (confirm(`确定要删除用户 ${user.name} 吗?`)) {
try {
await deleteUser(user.id)
} catch (error) {
// 错误已经在组合式函数中处理
}
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchUsers()
})
</script>// 🎉 高级组合式函数:状态持久化
// composables/useLocalStorage.js
import { ref, watch, Ref } from 'vue'
export function useLocalStorage<T>(
key: string,
defaultValue: T,
options: {
serializer?: {
read: (value: string) => T
write: (value: T) => string
}
} = {}
): [Ref<T>, (value: T) => void, () => void] {
const {
serializer = {
read: JSON.parse,
write: JSON.stringify
}
} = options
// 从localStorage读取初始值
const read = (): T => {
try {
const item = localStorage.getItem(key)
if (item === null) {
return defaultValue
}
return serializer.read(item)
} catch (error) {
console.error(`读取localStorage失败 (key: ${key}):`, error)
return defaultValue
}
}
// 写入localStorage
const write = (value: T): void => {
try {
localStorage.setItem(key, serializer.write(value))
} catch (error) {
console.error(`写入localStorage失败 (key: ${key}):`, error)
}
}
// 删除localStorage项
const remove = (): void => {
try {
localStorage.removeItem(key)
storedValue.value = defaultValue
} catch (error) {
console.error(`删除localStorage失败 (key: ${key}):`, error)
}
}
// 创建响应式引用
const storedValue = ref<T>(read())
// 监听值变化并同步到localStorage
watch(
storedValue,
(newValue) => {
write(newValue)
},
{ deep: true }
)
// 监听localStorage变化(跨标签页同步)
window.addEventListener('storage', (e) => {
if (e.key === key && e.newValue !== null) {
try {
storedValue.value = serializer.read(e.newValue)
} catch (error) {
console.error(`同步localStorage失败 (key: ${key}):`, error)
}
}
})
return [storedValue, write, remove]
}
// 使用示例
export function useUserPreferences() {
const [theme, setTheme] = useLocalStorage('user-theme', 'light')
const [language, setLanguage] = useLocalStorage('user-language', 'zh-CN')
const [settings, setSettings, clearSettings] = useLocalStorage('user-settings', {
notifications: true,
autoSave: true,
fontSize: 14
})
const toggleTheme = () => {
setTheme(theme.value === 'light' ? 'dark' : 'light')
}
const updateSetting = (key: string, value: any) => {
setSettings({
...settings.value,
[key]: value
})
}
return {
theme,
language,
settings,
setTheme,
setLanguage,
toggleTheme,
updateSetting,
clearSettings
}
}// 🎉 组合式函数的组合使用
// composables/useApi.js
import { ref } from 'vue'
export function useApi() {
const loading = ref(false)
const error = ref(null)
const request = async (apiCall) => {
loading.value = true
error.value = null
try {
const result = await apiCall()
return result
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
return {
loading: readonly(loading),
error: readonly(error),
request
}
}
// composables/useValidation.js
import { ref, computed } from 'vue'
export function useValidation(rules) {
const errors = ref({})
const validate = (data) => {
errors.value = {}
let isValid = true
Object.keys(rules).forEach(field => {
const fieldRules = rules[field]
const value = data[field]
const fieldErrors = []
fieldRules.forEach(rule => {
if (rule.required && (!value || value.trim() === '')) {
fieldErrors.push(rule.message || `${field}是必填项`)
}
if (rule.minLength && value && value.length < rule.minLength) {
fieldErrors.push(rule.message || `${field}最少${rule.minLength}个字符`)
}
if (rule.pattern && value && !rule.pattern.test(value)) {
fieldErrors.push(rule.message || `${field}格式不正确`)
}
})
if (fieldErrors.length > 0) {
errors.value[field] = fieldErrors
isValid = false
}
})
return isValid
}
const hasErrors = computed(() => Object.keys(errors.value).length > 0)
const getFieldError = (field) => errors.value[field] || []
return {
errors: readonly(errors),
hasErrors,
validate,
getFieldError
}
}
// 组合使用多个组合式函数
export function useUserForm() {
// 组合多个组合式函数
const { loading, error, request } = useApi()
const { theme } = useUserPreferences()
const validationRules = {
name: [
{ required: true, message: '姓名不能为空' },
{ minLength: 2, message: '姓名至少2个字符' }
],
email: [
{ required: true, message: '邮箱不能为空' },
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }
]
}
const { errors, hasErrors, validate, getFieldError } = useValidation(validationRules)
// 表单数据
const formData = ref({
name: '',
email: '',
phone: ''
})
// 提交表单
const submitForm = async () => {
if (!validate(formData.value)) {
return false
}
try {
const result = await request(() =>
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData.value)
}).then(res => res.json())
)
// 重置表单
formData.value = { name: '', email: '', phone: '' }
return result
} catch (err) {
return false
}
}
return {
// 表单状态
formData,
loading,
error,
errors,
hasErrors,
theme,
// 方法
submitForm,
getFieldError,
validate
}
}// 🎉 组合式函数最佳实践
// 1. 命名约定:使用use前缀
export function useFeatureName() {
// 实现逻辑
}
// 2. 返回值约定:使用对象解构
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
// 返回对象,便于解构和重命名
return {
count,
increment
}
}
// 3. 参数设计:支持配置选项
export function useApi(baseURL, options = {}) {
const {
timeout = 5000,
retries = 3,
headers = {}
} = options
// 实现逻辑
}
// 4. 类型安全:完整的TypeScript支持
export function useAsyncData<T>(
fetcher: () => Promise<T>,
options: {
immediate?: boolean
resetOnExecute?: boolean
} = {}
): {
data: Ref<T | null>
loading: Ref<boolean>
error: Ref<Error | null>
execute: () => Promise<void>
refresh: () => Promise<void>
} {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const execute = async () => {
loading.value = true
error.value = null
if (options.resetOnExecute) {
data.value = null
}
try {
data.value = await fetcher()
} catch (err) {
error.value = err as Error
} finally {
loading.value = false
}
}
const refresh = execute
if (options.immediate !== false) {
execute()
}
return {
data,
loading,
error,
execute,
refresh
}
}
// 5. 副作用清理:自动清理资源
export function useEventListener(
target: EventTarget,
event: string,
handler: EventListener,
options?: AddEventListenerOptions
) {
onMounted(() => {
target.addEventListener(event, handler, options)
})
onBeforeUnmount(() => {
target.removeEventListener(event, handler, options)
})
}
// 6. 条件执行:支持条件性启用
export function useInterval(
callback: () => void,
delay: number,
options: {
immediate?: boolean
enabled?: Ref<boolean>
} = {}
) {
const { immediate = false, enabled = ref(true) } = options
let timer: number | null = null
const start = () => {
if (timer) return
if (immediate) {
callback()
}
timer = setInterval(callback, delay)
}
const stop = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
watch(enabled, (isEnabled) => {
if (isEnabled) {
start()
} else {
stop()
}
}, { immediate: true })
onBeforeUnmount(stop)
return {
start,
stop
}
}组合式函数最佳实践总结:
💼 实际应用:组合式函数是Vue3推荐的代码复用方案,它解决了混入的诸多问题,提供了更好的开发体验和代码质量。
通过本节**组合式函数(Composables)**的学习,你已经掌握:
A: 组合式函数利用Vue的响应式系统,可以在函数内部使用ref、computed等响应式API,而普通函数不具备这些能力。
A: 当你发现多个组件中有相同的逻辑,或者某个功能足够复杂需要独立管理时,就应该考虑创建组合式函数。
A: 可以,组合式函数可以调用其他组合式函数,这是组合模式的核心优势之一。
A: 组合式函数可以独立测试,不需要创建组件实例。可以直接调用函数并测试返回的响应式数据和方法。
A: 组合式函数的性能通常比混入更好,因为它们支持按需引入和tree-shaking,只有实际使用的代码才会被打包。
// 组合式函数开发模板
function createComposableTemplate(name, features = []) {
const template = `
// composables/use${name}.js
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
export function use${name}(options = {}) {
// 状态
const state = ref(null)
const loading = ref(false)
const error = ref(null)
// 计算属性
const isReady = computed(() => !loading.value && !error.value)
// 方法
const initialize = async () => {
loading.value = true
error.value = null
try {
// 初始化逻辑
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 生命周期
onMounted(() => {
if (options.immediate !== false) {
initialize()
}
})
onBeforeUnmount(() => {
// 清理逻辑
})
return {
state: readonly(state),
loading: readonly(loading),
error: readonly(error),
isReady,
initialize
}
}
`
return template
}"组合式函数是Vue3的精髓所在,它体现了现代前端开发的最佳实践。掌握组合式函数,就是掌握了Vue3开发的核心竞争力。"