VueUse 完全学习指南:组合式API实用函数集合
🎯 什么是 VueUse?
VueUse 是基于 组合式API(Composition API) 的实用函数集合,为Vue 3开发者提供了丰富的可复用逻辑功能。它通过提供大量预构建的组合函数,帮助开发者快速实现常见功能,提升开发效率。
🌟 核心特点
- 基于组合式API:充分利用Vue 3的Composition API特性
- 函数式编程:提供可复用、可组合的逻辑函数
- 类型安全:完整的TypeScript支持
- 轻量级:按需导入,减少打包体积
- 生态丰富:涵盖传感器、动画、网络、状态管理等多个领域
- 跨平台:支持Vue 3、Nuxt 3等多种环境
📦 安装与配置
NPM 安装
# 核心包
npm i @vueuse/core
# 完整安装(包含所有插件)
npm i @vueuse/core @vueuse/components @vueuse/integrations
CDN 引入
<!-- 共享工具 -->
<script src="https://unpkg.com/@vueuse/shared"></script>
<!-- 核心功能 -->
<script src="https://unpkg.com/@vueuse/core"></script>
<script>
// 作为 window.VueUse 全局可用
const { useMouse, useLocalStorage } = window.VueUse
</script>
Nuxt 3 集成
自动安装模块
# 使用 nuxi 安装
npx nuxi@latest module add vueuse
# 或使用 npm
npm i -D @vueuse/nuxt @vueuse/core
配置文件
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@vueuse/nuxt',
],
})
在Nuxt中使用
<script setup lang="ts">
// 无需导入,自动可用
const { x, y } = useMouse()
const isDark = usePreferredDark()
</script>
<template>
<div>
<p>鼠标位置: {{ x }}, {{ y }}</p>
<p>深色模式: {{ isDark ? '开启' : '关闭' }}</p>
</div>
</template>
🚀 基础使用
简单示例
<script setup>
import { useLocalStorage, useMouse, usePreferredDark } from '@vueuse/core'
// 鼠标位置追踪
const { x, y } = useMouse()
// 用户主题偏好
const isDark = usePreferredDark()
// 本地存储状态持久化
const store = useLocalStorage('my-storage', {
name: 'Apple',
color: 'red',
})
</script>
<template>
<div>
<h2>鼠标位置</h2>
<p>x: {{ x }}, y: {{ y }}</p>
<h2>主题设置</h2>
<p>用户偏好深色模式: {{ isDark }}</p>
<h2>本地存储</h2>
<input v-model="store.name" placeholder="名称" />
<input v-model="store.color" placeholder="颜色" />
<p>存储内容: {{ store }}</p>
</div>
</template>
📚 核心功能分类
1. 🔍 传感器 (Sensors)
useMouse - 鼠标位置追踪
<script setup>
import { useMouse } from '@vueuse/core'
const { x, y, sourceType } = useMouse()
</script>
<template>
<div>
<p>坐标: ({{ x }}, {{ y }})</p>
<p>输入类型: {{ sourceType }}</p>
</div>
</template>
useDeviceOrientation - 设备方向
<script setup>
import { useDeviceOrientation } from '@vueuse/core'
const {
isAbsolute,
alpha, // Z轴旋转 (0-360)
beta, // X轴旋转 (-180到180)
gamma, // Y轴旋转 (-90到90)
} = useDeviceOrientation()
</script>
<template>
<div>
<p>设备方向 - α: {{ alpha?.toFixed(1) }}°</p>
<p>设备方向 - β: {{ beta?.toFixed(1) }}°</p>
<p>设备方向 - γ: {{ gamma?.toFixed(1) }}°</p>
</div>
</template>
useGeolocation - 地理位置
<script setup>
import { useGeolocation } from '@vueuse/core'
const { coords, locatedAt, error, resume, pause } = useGeolocation()
</script>
<template>
<div>
<div v-if="error">获取位置失败: {{ error.message }}</div>
<div v-else>
<p>纬度: {{ coords.latitude }}</p>
<p>经度: {{ coords.longitude }}</p>
<p>精度: {{ coords.accuracy }}米</p>
<p>定位时间: {{ locatedAt }}</p>
</div>
<button @click="resume">开始定位</button>
<button @click="pause">停止定位</button>
</div>
</template>
2. 🌐 浏览器 (Browser)
useLocalStorage - 本地存储
<script setup>
import { useLocalStorage } from '@vueuse/core'
// 基础用法
const counter = useLocalStorage('counter', 0)
// 对象存储
const user = useLocalStorage('user', {
name: '张三',
age: 25,
preferences: {
theme: 'dark',
language: 'zh-CN'
}
})
// 自定义序列化
const settings = useLocalStorage('settings', new Set(), {
serializer: {
read: (v) => new Set(JSON.parse(v)),
write: (v) => JSON.stringify(Array.from(v))
}
})
</script>
<template>
<div>
<h3>计数器</h3>
<p>当前值: {{ counter }}</p>
<button @click="counter++">增加</button>
<button @click="counter--">减少</button>
<h3>用户信息</h3>
<input v-model="user.name" placeholder="姓名" />
<input v-model.number="user.age" placeholder="年龄" />
<select v-model="user.preferences.theme">
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
</template>
useClipboard - 剪贴板操作
<script setup>
import { useClipboard } from '@vueuse/core'
const { text, copy, copied, isSupported } = useClipboard()
const input = ref('')
const copyText = async () => {
await copy(input.value)
}
</script>
<template>
<div>
<div v-if="!isSupported">浏览器不支持剪贴板API</div>
<div v-else>
<input v-model="input" placeholder="输入要复制的文本" />
<button @click="copyText">复制</button>
<span v-if="copied" class="success">✓ 已复制</span>
<h4>剪贴板内容:</h4>
<p>{{ text || '无内容' }}</p>
</div>
</div>
</template>
<style scoped>
.success {
color: green;
margin-left: 8px;
}
</style>
3. 🎨 元素 (Elements)
useElementSize - 元素尺寸监听
<script setup>
import { useElementSize } from '@vueuse/core'
const el = ref()
const { width, height } = useElementSize(el)
</script>
<template>
<div>
<div
ref="el"
class="resizable-box"
:style="{ resize: 'both', overflow: 'auto' }"
>
<p>拖拽右下角调整大小</p>
<p>宽度: {{ Math.round(width) }}px</p>
<p>高度: {{ Math.round(height) }}px</p>
</div>
</div>
</template>
<style scoped>
.resizable-box {
border: 2px solid #ccc;
padding: 20px;
min-width: 200px;
min-height: 100px;
background: #f9f9f9;
}
</style>
useIntersectionObserver - 交叉观察器
<script setup>
import { useIntersectionObserver } from '@vueuse/core'
const target = ref()
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }]) => {
targetIsVisible.value = isIntersecting
},
{
threshold: 0.5 // 50%可见时触发
}
)
</script>
<template>
<div>
<div style="height: 200vh">
<p>向下滚动查看效果</p>
<div style="height: 50vh"></div>
<div
ref="target"
:class="{ visible: targetIsVisible }"
class="target-element"
>
<p>目标元素</p>
<p>可见状态: {{ targetIsVisible ? '可见' : '不可见' }}</p>
</div>
<div style="height: 100vh"></div>
</div>
</div>
</template>
<style scoped>
.target-element {
padding: 20px;
background: #f0f0f0;
border: 2px solid #ddd;
transition: all 0.3s ease;
}
.target-element.visible {
background: #e8f5e8;
border-color: #4caf50;
transform: scale(1.05);
}
</style>
4. ⏰ 时间 (Time)
useNow - 实时时间
<script setup>
import { useNow, useDateFormat } from '@vueuse/core'
const now = useNow()
const formatted = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss')
// 自定义更新间隔
const nowWithInterval = useNow({ interval: 1000 })
</script>
<template>
<div>
<h3>实时时间</h3>
<p>当前时间: {{ formatted }}</p>
<p>时间戳: {{ now.getTime() }}</p>
<h3>秒级更新</h3>
<p>{{ useDateFormat(nowWithInterval, 'HH:mm:ss') }}</p>
</div>
</template>
useTimeAgo - 相对时间
<script setup>
import { useTimeAgo } from '@vueuse/core'
const time1 = new Date(Date.now() - 30000) // 30秒前
const time2 = new Date(Date.now() - 300000) // 5分钟前
const time3 = new Date(Date.now() - 86400000) // 1天前
const timeAgo1 = useTimeAgo(time1)
const timeAgo2 = useTimeAgo(time2)
const timeAgo3 = useTimeAgo(time3)
</script>
<template>
<div>
<h3>相对时间显示</h3>
<p>30秒前的时间: {{ timeAgo1 }}</p>
<p>5分钟前的时间: {{ timeAgo2 }}</p>
<p>1天前的时间: {{ timeAgo3 }}</p>
</div>
</template>
5. 🌐 网络 (Network)
useFetch - HTTP请求
<script setup>
import { useFetch } from '@vueuse/core'
// 基础用法
const { data, error, isFetching } = useFetch('https://api.github.com/users/vuejs')
// 带选项的用法
const {
data: posts,
error: postsError,
isFetching: isLoadingPosts,
execute: refetchPosts
} = useFetch('https://jsonplaceholder.typicode.com/posts', {
refetch: false, // 禁用自动请求
}).json()
// POST 请求
const createPost = () => {
return useFetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '新文章',
body: '文章内容',
userId: 1,
}),
}).json()
}
</script>
<template>
<div>
<h3>GitHub用户信息</h3>
<div v-if="isFetching">加载中...</div>
<div v-else-if="error">错误: {{ error }}</div>
<div v-else-if="data">
<img :src="data.avatar_url" width="50" />
<p>用户名: {{ data.login }}</p>
<p>名称: {{ data.name }}</p>
</div>
<h3>文章列表</h3>
<button @click="refetchPosts" :disabled="isLoadingPosts">
{{ isLoadingPosts ? '加载中...' : '获取文章' }}
</button>
<div v-if="posts">
<div v-for="post in posts.slice(0, 5)" :key="post.id" class="post">
<h4>{{ post.title }}</h4>
<p>{{ post.body }}</p>
</div>
</div>
</div>
</template>
<style scoped>
.post {
border: 1px solid #ddd;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
}
</style>
6. 🎯 状态管理 (State)
useToggle - 切换状态
<script setup>
import { useToggle } from '@vueuse/core'
// 基础布尔切换
const [isVisible, toggle] = useToggle()
// 带默认值的切换
const [theme, toggleTheme] = useToggle('light', 'dark')
// 数组值切换
const [status, toggleStatus] = useToggle(['pending', 'success', 'error'])
</script>
<template>
<div>
<h3>基础切换</h3>
<button @click="toggle()">
{{ isVisible ? '隐藏' : '显示' }}
</button>
<p v-show="isVisible">这是一个可切换的内容</p>
<h3>主题切换</h3>
<button @click="toggleTheme()">
当前主题: {{ theme }}
</button>
<h3>状态循环</h3>
<button @click="toggleStatus()">
当前状态: {{ status }}
</button>
</div>
</template>
useCounter - 计数器
<script setup>
import { useCounter } from '@vueuse/core'
const { count, inc, dec, set, reset } = useCounter(0, {
min: 0,
max: 10
})
</script>
<template>
<div>
<h3>计数器 (0-10)</h3>
<p>当前值: {{ count }}</p>
<div class="controls">
<button @click="dec()" :disabled="count <= 0">-1</button>
<button @click="inc()" :disabled="count >= 10">+1</button>
<button @click="inc(5)">+5</button>
<button @click="set(5)">设为5</button>
<button @click="reset()">重置</button>
</div>
</div>
</template>
<style scoped>
.controls {
display: flex;
gap: 8px;
margin-top: 10px;
}
.controls button {
padding: 4px 12px;
}
</style>
🎨 实际应用案例
案例1:响应式仪表板
<script setup>
import {
useWindowSize,
usePreferredDark,
useLocalStorage,
useElementSize,
useNow,
useDateFormat
} from '@vueuse/core'
// 窗口尺寸
const { width: windowWidth, height: windowHeight } = useWindowSize()
// 主题管理
const isDark = usePreferredDark()
const userTheme = useLocalStorage('user-theme', 'auto')
// 组件尺寸
const dashboardRef = ref()
const { width: dashboardWidth, height: dashboardHeight } = useElementSize(dashboardRef)
// 实时时间
const now = useNow()
const currentTime = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss')
// 用户设置
const settings = useLocalStorage('dashboard-settings', {
showClock: true,
showStats: true,
refreshInterval: 5000,
})
// 计算属性
const isCompact = computed(() => windowWidth.value < 768)
const themeClass = computed(() => {
if (userTheme.value === 'auto') {
return isDark.value ? 'dark' : 'light'
}
return userTheme.value
})
</script>
<template>
<div
ref="dashboardRef"
:class="['dashboard', themeClass, { compact: isCompact }]"
>
<!-- 头部信息 -->
<header class="dashboard-header">
<h1>响应式仪表板</h1>
<div v-if="settings.showClock" class="clock">
{{ currentTime }}
</div>
</header>
<!-- 统计卡片 -->
<div v-if="settings.showStats" class="stats-grid">
<div class="stat-card">
<h3>窗口尺寸</h3>
<p>{{ windowWidth }} × {{ windowHeight }}</p>
</div>
<div class="stat-card">
<h3>仪表板尺寸</h3>
<p>{{ Math.round(dashboardWidth) }} × {{ Math.round(dashboardHeight) }}</p>
</div>
<div class="stat-card">
<h3>设备类型</h3>
<p>{{ isCompact ? '移动设备' : '桌面设备' }}</p>
</div>
<div class="stat-card">
<h3>主题偏好</h3>
<p>{{ isDark ? '深色模式' : '浅色模式' }}</p>
</div>
</div>
<!-- 设置面板 -->
<div class="settings-panel">
<h3>设置</h3>
<label>
<input v-model="settings.showClock" type="checkbox" />
显示时钟
</label>
<label>
<input v-model="settings.showStats" type="checkbox" />
显示统计
</label>
<label>
主题选择:
<select v-model="userTheme">
<option value="auto">自动</option>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</label>
</div>
</div>
</template>
<style scoped>
.dashboard {
padding: 20px;
min-height: 100vh;
transition: all 0.3s ease;
}
.dashboard.light {
background: #f5f5f5;
color: #333;
}
.dashboard.dark {
background: #1a1a1a;
color: #fff;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid currentColor;
}
.clock {
font-family: monospace;
font-size: 1.2em;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.dashboard.compact .stats-grid {
grid-template-columns: 1fr;
}
.stat-card {
padding: 16px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dashboard.light .stat-card {
background: rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.1);
}
.settings-panel {
padding: 20px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
}
.settings-panel label {
display: block;
margin: 10px 0;
}
.settings-panel select {
margin-left: 10px;
padding: 4px;
}
</style>
案例2:图片懒加载组件
<script setup>
import { useIntersectionObserver, useImage } from '@vueuse/core'
const props = defineProps({
src: String,
alt: String,
placeholder: String,
threshold: {
type: Number,
default: 0.1
}
})
const target = ref()
const targetIsVisible = ref(false)
const shouldLoad = ref(false)
// 交叉观察器
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }]) => {
targetIsVisible.value = isIntersecting
if (isIntersecting && !shouldLoad.value) {
shouldLoad.value = true
stop() // 停止观察,避免重复加载
}
},
{
threshold: props.threshold
}
)
// 图片加载状态
const { isLoading, error } = useImage({
src: computed(() => shouldLoad.value ? props.src : ''),
loading: 'lazy'
})
const currentSrc = computed(() => {
if (error.value) return props.placeholder
if (!shouldLoad.value) return props.placeholder
if (isLoading.value) return props.placeholder
return props.src
})
</script>
<template>
<div
ref="target"
class="lazy-image-container"
:class="{
loading: isLoading,
error: error,
visible: targetIsVisible
}"
>
<img
:src="currentSrc"
:alt="alt"
class="lazy-image"
@load="$emit('loaded')"
@error="$emit('error')"
/>
<div v-if="isLoading" class="loading-indicator">
<div class="spinner"></div>
</div>
<div v-if="error" class="error-indicator">
❌ 加载失败
</div>
</div>
</template>
<style scoped>
.lazy-image-container {
position: relative;
display: inline-block;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.lazy-image {
width: 100%;
height: auto;
transition: opacity 0.3s ease;
}
.loading-indicator,
.error-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #ddd;
border-top: 2px solid #666;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.lazy-image-container.loading .lazy-image {
opacity: 0.3;
}
.lazy-image-container.error .lazy-image {
opacity: 0.5;
filter: grayscale(100%);
}
</style>
案例3:全局状态管理
<!-- composables/useGlobalState.js -->
<script>
import { createGlobalState, useStorage } from '@vueuse/core'
// 创建全局状态
export const useGlobalState = createGlobalState(() => {
// 用户信息
const user = useStorage('user', {
id: null,
name: '',
avatar: '',
isLoggedIn: false
})
// 应用设置
const appSettings = useStorage('app-settings', {
theme: 'auto',
language: 'zh-CN',
notifications: true
})
// 购物车
const cart = useStorage('shopping-cart', [])
// 方法
const login = (userData) => {
user.value = {
...userData,
isLoggedIn: true
}
}
const logout = () => {
user.value = {
id: null,
name: '',
avatar: '',
isLoggedIn: false
}
}
const addToCart = (item) => {
const existingItem = cart.value.find(cartItem => cartItem.id === item.id)
if (existingItem) {
existingItem.quantity += 1
} else {
cart.value.push({ ...item, quantity: 1 })
}
}
const removeFromCart = (itemId) => {
const index = cart.value.findIndex(item => item.id === itemId)
if (index > -1) {
cart.value.splice(index, 1)
}
}
const updateTheme = (theme) => {
appSettings.value.theme = theme
}
// 计算属性
const cartTotal = computed(() => {
return cart.value.reduce((total, item) => total + item.price * item.quantity, 0)
})
const cartItemCount = computed(() => {
return cart.value.reduce((count, item) => count + item.quantity, 0)
})
return {
// 状态
user: readonly(user),
appSettings: readonly(appSettings),
cart: readonly(cart),
// 方法
login,
logout,
addToCart,
removeFromCart,
updateTheme,
// 计算属性
cartTotal,
cartItemCount
}
})
</script>
<!-- 使用示例 -->
<script setup>
import { useGlobalState } from './composables/useGlobalState'
const {
user,
cart,
cartTotal,
cartItemCount,
addToCart,
removeFromCart,
login,
logout
} = useGlobalState()
const sampleProducts = [
{ id: 1, name: 'iPhone 15', price: 5999 },
{ id: 2, name: 'MacBook Pro', price: 12999 },
{ id: 3, name: 'AirPods', price: 1299 }
]
</script>
<template>
<div class="app">
<!-- 头部 -->
<header class="header">
<h1>电商应用</h1>
<div class="user-section">
<div v-if="user.isLoggedIn" class="user-info">
<span>欢迎,{{ user.name }}</span>
<button @click="logout">登出</button>
</div>
<button v-else @click="login({ id: 1, name: '张三', avatar: '' })">
登录
</button>
<div class="cart-info">
🛒 {{ cartItemCount }} 项商品
<span v-if="cartItemCount > 0">
- ¥{{ cartTotal.toFixed(2) }}
</span>
</div>
</div>
</header>
<!-- 商品列表 -->
<main class="main">
<h2>商品列表</h2>
<div class="products">
<div v-for="product in sampleProducts" :key="product.id" class="product-card">
<h3>{{ product.name }}</h3>
<p>¥{{ product.price }}</p>
<button @click="addToCart(product)">加入购物车</button>
</div>
</div>
<!-- 购物车 -->
<div v-if="cart.length > 0" class="cart">
<h2>购物车</h2>
<div v-for="item in cart" :key="item.id" class="cart-item">
<span>{{ item.name }}</span>
<span>数量: {{ item.quantity }}</span>
<span>¥{{ (item.price * item.quantity).toFixed(2) }}</span>
<button @click="removeFromCart(item.id)">删除</button>
</div>
<div class="cart-total">
总计: ¥{{ cartTotal.toFixed(2) }}
</div>
</div>
</main>
</div>
</template>
<style scoped>
.app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.user-section {
display: flex;
align-items: center;
gap: 20px;
}
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 40px;
}
.product-card {
border: 1px solid #ddd;
padding: 16px;
border-radius: 8px;
text-align: center;
}
.cart {
border-top: 1px solid #ddd;
padding-top: 20px;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.cart-total {
font-size: 1.2em;
font-weight: bold;
text-align: right;
margin-top: 10px;
}
</style>
🛠️ 最佳实践
1. 性能优化
<script setup>
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
// 防抖搜索
const searchQuery = ref('')
const searchResults = ref([])
const debouncedSearch = useDebounceFn(async (query) => {
if (query.length < 2) {
searchResults.value = []
return
}
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
searchResults.value = await response.json()
} catch (error) {
console.error('搜索失败:', error)
}
}, 300)
watch(searchQuery, debouncedSearch)
// 节流滚动事件
const scrollY = ref(0)
const throttledScrollHandler = useThrottleFn(() => {
scrollY.value = window.scrollY
}, 100)
onMounted(() => {
window.addEventListener('scroll', throttledScrollHandler)
})
onUnmounted(() => {
window.removeEventListener('scroll', throttledScrollHandler)
})
</script>
2. 组合函数封装
// composables/useApi.js
import { useFetch } from '@vueuse/core'
export function useApi(endpoint, options = {}) {
const baseURL = 'https://api.example.com'
const {
data,
error,
isFetching,
execute,
...rest
} = useFetch(`${baseURL}${endpoint}`, {
beforeFetch({ url, options }) {
// 添加认证头
const token = localStorage.getItem('auth-token')
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`
}
}
return { url, options }
},
afterFetch(ctx) {
// 处理响应数据
if (ctx.data && ctx.data.success === false) {
throw new Error(ctx.data.message || '请求失败')
}
return ctx
},
onFetchError(ctx) {
// 错误处理
console.error('API请求错误:', ctx.error)
return ctx
},
...options
}).json()
return {
data,
error,
loading: isFetching,
refetch: execute,
...rest
}
}
// 使用示例
export function useUserList() {
return useApi('/users')
}
export function useUserDetail(userId) {
return useApi(computed(() => `/users/${userId.value}`), {
refetch: false
})
}
3. 错误处理
<script setup>
import { useErrorHandler } from '@vueuse/core'
// 全局错误处理
const { onError } = useErrorHandler()
onError((error, instance, info) => {
console.error('Vue错误:', error)
console.error('组件实例:', instance)
console.error('错误信息:', info)
// 发送错误报告
reportError({
message: error.message,
stack: error.stack,
component: instance?.$options.name,
info
})
})
// API错误处理
const { data, error } = useFetch('/api/data', {
onFetchError(ctx) {
// 统一错误处理
const errorMessage = ctx.data?.message || '网络请求失败'
ElMessage.error(errorMessage)
// 根据错误码处理
if (ctx.response?.status === 401) {
// 未授权,跳转登录
router.push('/login')
}
return ctx
}
})
function reportError(errorInfo) {
// 发送到错误监控服务
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorInfo)
}).catch(console.error)
}
</script>
🎯 总结
VueUse 是Vue 3生态系统中不可或缺的工具库,它提供了:
✅ 丰富的功能:涵盖传感器、浏览器、网络、状态管理等各个方面
✅ 开箱即用:无需复杂配置,直接导入使用
✅ 类型安全:完整的TypeScript支持,提供良好的开发体验
✅ 性能优秀:按需导入,优化包体积
✅ 生态完善:与Nuxt、Vue Router等完美集成
✅ 持续更新:活跃的社区维护,功能不断丰富
💡 学习建议
- 从基础开始:先掌握常用的状态管理和浏览器相关函数
- 结合实际项目:在真实项目中实践,加深理解
- 阅读源码:学习优秀的组合式API设计模式
- 创建自定义组合函数:根据项目需求封装可复用逻辑
- 关注生态发展:跟进新功能和最佳实践
📚 推荐学习路径
第一阶段:基础入门
├── useLocalStorage, useSessionStorage (状态持久化)
├── useMouse, useWindowSize (传感器)
├── useToggle, useCounter (状态管理)
└── useFetch (网络请求)
第二阶段:进阶应用
├── useIntersectionObserver (性能优化)
├── useDebounceFn, useThrottleFn (性能优化)
├── useClipboard, usePermission (浏览器API)
└── createGlobalState (全局状态)
第三阶段:高级应用
├── 自定义组合函数设计
├── 插件开发和集成
├── 性能优化实践
└── 大型项目架构设计
通过系统学习和实践VueUse,您可以大大提高Vue 3开发效率,写出更加优雅和可维护的代码。
开始您的VueUse学习之旅吧! 🚀
💡 开发建议:建议从项目实际需求出发,逐步引入VueUse函数,避免过度使用。重点关注代码的可读性和可维护性。