VueUse:组合式API实用函数全集

news2025/6/6 7:19:36

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等完美集成
持续更新:活跃的社区维护,功能不断丰富

💡 学习建议

  1. 从基础开始:先掌握常用的状态管理和浏览器相关函数
  2. 结合实际项目:在真实项目中实践,加深理解
  3. 阅读源码:学习优秀的组合式API设计模式
  4. 创建自定义组合函数:根据项目需求封装可复用逻辑
  5. 关注生态发展:跟进新功能和最佳实践

📚 推荐学习路径

第一阶段:基础入门
├── useLocalStorage, useSessionStorage (状态持久化)
├── useMouse, useWindowSize (传感器)
├── useToggle, useCounter (状态管理)
└── useFetch (网络请求)

第二阶段:进阶应用
├── useIntersectionObserver (性能优化)
├── useDebounceFn, useThrottleFn (性能优化)
├── useClipboard, usePermission (浏览器API)
└── createGlobalState (全局状态)

第三阶段:高级应用
├── 自定义组合函数设计
├── 插件开发和集成
├── 性能优化实践
└── 大型项目架构设计

通过系统学习和实践VueUse,您可以大大提高Vue 3开发效率,写出更加优雅和可维护的代码。


开始您的VueUse学习之旅吧! 🚀

💡 开发建议:建议从项目实际需求出发,逐步引入VueUse函数,避免过度使用。重点关注代码的可读性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2398314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《自动驾驶轨迹规划实战:Lattice Planner实现避障路径生成(附可运行Python代码)》—— 零基础实现基于离散优化的避障路径规划

《自动驾驶轨迹规划实战&#xff1a;Lattice Planner实现避障路径生成&#xff08;附可运行Python代码&#xff09;》 —— 零基础实现基于离散优化的避障路径规划 一、为什么Lattice Planner成为自动驾驶的核心算法&#xff1f; 在自动驾驶的路径规划领域&#xff0c;Lattice…

PyTorch——卷积操作(2)

二维矩阵 [[ ]] 这里面conv2d(N,C,H,W)里面的四个是 N就是batch size也就是输入图片的数量&#xff0c;C就是通道数这只是一个二维张量所以通道为1&#xff0c;H就是高&#xff0c;W就是宽&#xff0c;所以是1 1 5 5 卷积核 reshape 第一个参数是batch size样本数量 第二个参数…

【JavaWeb】SpringBoot原理

1 配置优先级 在前面&#xff0c;已经学习了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;通过这三种方式当中的任意一种来配置都可以&a…

ubuntu22.04安装taskfile

sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -dsudo mv ./bin/task /usr/local/bin/测试 task --version

遥感影像建筑物变化检测

文章目录 效果1、环境安装2、项目下载3、数据集下载4、模型训练5、模型推理6、推理结果7、批量推理效果 1、环境安装 参考文章 搭建Pytorch的GPU环境超详细 win10安装3DGS环境(GPU)超详细 测试GPU环境可用 2、项目下载 https://gitcode.com/gh_mirrors/ch/change_detectio…

多模态大语言模型arxiv论文略读(103)

Are Bigger Encoders Always Better in Vision Large Models? ➡️ 论文标题&#xff1a;Are Bigger Encoders Always Better in Vision Large Models? ➡️ 论文作者&#xff1a;Bozhou Li, Hao Liang, Zimo Meng, Wentao Zhang ➡️ 研究机构: 北京大学 ➡️ 问题背景&…

汇编语言基础: 搭建实验环境

环境配置 1.Visual Studio 创建空项目 创建成功 2.平台框架改为为WIN32 右键点击项目 点击属性 点击配置管理器 平台改为Win32(本文使用32位的汇编) 3.生成采用MASM 在项目属性里点击"生成依赖项"的"生成自定义" 勾选 masm 4.创建第一个汇编程序 右…

基于springboot的益智游戏系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

第十二节:第四部分:集合框架:List系列集合:LinkedList集合的底层原理、特有方法、栈、队列

LinkedList集合的底层原理 LinkedList集合的应用场景之一 代码&#xff1a;掌握LinkedList集合的使用 package com.itheima.day19_Collection_List;import java.util.LinkedList; import java.util.List;//掌握LinkedList集合的使用。 public class ListTest3 {public static …

多模态大语言模型arxiv论文略读(104)

Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文标题&#xff1a;Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文作者&#xff1a;Yilun Hua, Yoav…

【C++高级主题】多重继承下的类作用域

目录 一、类作用域与名字查找规则&#xff1a;理解二义性的根源 1.1 类作用域的基本概念 1.2 单继承的名字查找流程 1.3 多重继承的名字查找特殊性 1.4 关键规则&#xff1a;“最近” 作用域优先&#xff0c;但多重继承无 “最近” 二、多重继承二义性的典型类型与代码示…

基于Android的一周穿搭APP的设计与实现 _springboot+vue

开发语言&#xff1a;Java框架&#xff1a;springboot AndroidJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6 系统展示 APP登录 A…

机器学习——使用多个决策树

使用单一决策树的弱点之一是决策树对数据中的微小变化非常敏感&#xff0c;一个使算法不那么敏感或更健壮的解决方案&#xff0c;不是建立一个决策树&#xff0c;而是要建立大量的决策树&#xff0c;我们称之为树合奏。 在这个例子中&#xff0c;我们一直在使用最好的特性来分…

C# 中的对话框与导航:构建流畅用户交互的完整指南

在现代应用程序开发中&#xff0c;良好的用户交互体验是成功的关键因素之一。作为.NET开发者&#xff0c;熟练掌握C#中的对话框与导航技术&#xff0c;能够显著提升应用程序的易用性和专业性。本文将全面探讨Windows Forms、WPF、ASP.NET Core和MAUI等平台下的对话框与导航实现…

DeepSeek - 尝试一下GitHub Models中的DeepSeek

1.简单介绍 当前DeepSeek使用的人很多&#xff0c;各大AI平台中也快速引入了DeekSeek&#xff0c;比如Azure AI Foundary(以前名字是Azure AI Studio)中的Model Catalog, HuggingFace, GitHub Models等。同时也出现了一些支持DeepSeek的.NET类库。微软的Semantic Kernel也支持…

【判断酒酒花数】2022-3-31

缘由对超长正整数的处理&#xff1f; - C语言论坛 - 编程论坛 void 判断酒酒花数(_int64 n) {//缘由https://bbs.bccn.net/thread-508634-1-1.html_int64 t n; int h 0, j 0;//while (j < 3)h t % 10, t / 10, j;//整数的个位十位百位之和是其前缀while (t > 0)h t…

【OCCT+ImGUI系列】011-Poly-Poly_Triangle三角形面片

Poly_Triangle 是什么&#xff1f; Poly_Triangle 是一个非常轻量的类&#xff0c;用于表示一个三角网格中的单个三角形面片。它是构成 Poly_Triangulation&#xff08;三角网格对象&#xff09;的基本单位之一。之后会写关于碰撞检测的相关文章&#xff0c;三角面片是非常重要…

【机器学习基础】机器学习入门核心算法:Mini-Batch K-Means算法

机器学习入门核心算法&#xff1a;Mini-Batch K-Means算法 一、算法逻辑工作流程与传统K-Means对比 二、算法原理与数学推导1. 目标函数2. Mini-Batch更新规则3. 学习率衰减机制4. 伪代码 三、模型评估1. 内部评估指标2. 收敛性判断3. 超参数调优 四、应用案例1. 图像处理 - 颜…

机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现

大家好,我是微学AI,今天给大家介绍一下机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现。 文章目录 一、项目介绍二、项目背景三、数学原理与算法分析动态规划模型遗传算法设计编码方案适应度函数约束处理算法参数能量消耗模型一泵房能耗二泵房能耗效率计算模…

【仿muduo库实现并发服务器】使用正则表达式提取HTTP元素

使用正则表达式提取HTTP元素 1.正则表达式2.正则库的使用3.使用正则表达式提取HTTP请求行 1.正则表达式 正则表达式它其实是描述了一种字符串匹配的模式&#xff0c;它可以用来在一个字符串中检测一个特定格式的字串&#xff0c;以及可以将符合特定规则的字串进行替换或者提取…