VueUse 常用 Hooks 使用指南

3,038 阅读12分钟

VueUse 常用 Hooks 使用指南

VueUse 是一个基于 Vue Composition API 的实用工具库,提供了大量开箱即用的功能。本文将介绍 15 个最常用的 VueUse Hooks,帮助您提升开发效率。

下面是 VueUse 常用 hooks 的使用场景总结,帮助您在不同开发需求中快速选择合适的工具:

🕒 时间处理

useNow + useDateFormat

  • 使用场景:实时时钟显示、倒计时组件、需要精确时间戳的日志系统
  • 优势:自动更新时间,无需手动处理定时器

🔄 双向数据绑定

useVModel

  • 使用场景:自定义表单组件、复杂输入控件、需要简化 v-model 逻辑的场景
  • 优势:减少父子组件通信的样板代码

🌐 URL 操作

useUrlSearchParams

  • 使用场景
    • 分页组件
    • 筛选和排序功能
    • 分享链接状态保持
  • 优势:自动同步 URL 参数与组件状态

📋 剪贴板操作

useClipboard

  • 使用场景
    • "复制链接"按钮
    • 代码片段分享
    • 数据导出功能
  • 优势:提供复制状态反馈,简化 Clipboard API 使用

📏 元素尺寸与位置

useElementBounding

  • 使用场景
    • 拖拽组件
    • 定位浮层/工具提示
    • 滚动动画效果

useElementSize

  • 使用场景
    • 响应式图表容器
    • 自适应布局组件
    • 元素尺寸变化监听

👀 可视区域检测

useElementVisibility

  • 使用场景
    • 图片/内容懒加载
    • 滚动到视图时的动画触发
    • 用户阅读进度跟踪

⏱️ 性能优化

useDebounceFn

  • 使用场景
    • 搜索框输入
    • 窗口大小调整事件
    • 高频触发的事件处理
  • 优势:减少不必要的计算和请求

🧬 DOM 变化监听

useMutationObserver

  • 使用场景
    • 第三方组件集成
    • 富文本编辑器
    • 动态内容注入监控

📌 全局状态管理

createGlobalState

  • 使用场景
    • 用户偏好设置(主题、语言)
    • 简单的全局计数器
    • 跨组件共享的小型状态

createInjectionState

  • 使用场景
    • 组件库开发
    • 复杂表单状态管理
    • 深层嵌套组件通信

🖱️ UI 交互

vOnClickOutside

  • 使用场景
    • 下拉菜单关闭
    • 模态框关闭
    • 弹出层隐藏

🔄 状态切换

useToggle

  • 使用场景
    • 开关组件
    • 显示/隐藏切换
    • 多状态切换控制
  • 优势:简化布尔值状态管理

📐 响应式布局

useResizeObserver

  • 使用场景
    • 自适应组件
    • 画布尺寸调整
    • 响应式图表重绘

状态持久化

useLocalStorageuseSessionStorage

​使用场景​​:

  • 用户偏好设置(主题、字体大小)持久化
  • 表单草稿自动保存
  • 跨页面状态共享(如购物车)

使用场景对照表

需求场景推荐 Hook
实时显示时间useNow + useDateFormat
自定义表单组件useVModel
URL 参数管理useUrlSearchParams
复制到剪贴板功能useClipboard
元素位置信息获取useElementBounding
懒加载实现useElementVisibility
搜索框防抖useDebounceFn
监听DOM变化useMutationObserver
全局主题切换createGlobalState
复杂表单状态共享createInjectionState
点击外部关闭弹窗vOnClickOutside
开关状态管理useToggle
响应式布局实现useResizeObserver
元素尺寸获取useElementSize

📍 1. useNow - 实时获取当前时间

<script setup>
import { useNow, useDateFormat } from '@vueuse/core'

// 获取当前时间响应式对象
const now = useNow()

// 格式化时间
const formattedTime = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss')
</script>

<template>
  <div class="card">
    <h3>useNow & useDateFormat</h3>
    <p>当前时间: {{ formattedTime }}</p>
  </div>
</template>

2. useVModel - 简化 v-model 绑定

<script setup>
import { useVModel } from '@vueuse/core'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

// 创建双向绑定的 ref
const value = useVModel(props, 'modelValue', emit)

// 示例:在输入框中使用
const handleInput = (e) => value.value = e.target.value
</script>

<template>
  <div class="card">
    <h3>useVModel</h3>
    <input :value="value" @input="handleInput" placeholder="输入内容...">
    <p>绑定值: {{ value }}</p>
  </div>
</template>

👀 3. useElementVisibility - 检测元素可见性

<script setup>
import { ref } from 'vue'
import { useElementVisibility } from '@vueuse/core'

const el = ref(null)
const isVisible = useElementVisibility(el)
</script>

<template>
  <div class="card">
    <h3>useElementVisibility</h3>
    <div ref="el" class="visibility-box">
      {{ isVisible ? '元素在视窗中' : '元素不在视窗中' }}
    </div>
    <div class="spacer"></div>
  </div>
</template>

<style>
.visibility-box {
  height: 150px;
  background: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  border-radius: 8px;
}

.spacer {
  height: 200vh;
}
</style>

🔍 4. useUrlSearchParams - 操作 URL 查询参数

<script setup>
import { useUrlSearchParams } from '@vueuse/core'

// 使用 history 模式
const params = useUrlSearchParams('history')

// 示例:设置搜索参数
const setSearchParam = () => {
  params.search = 'VueUse'
}
</script>

<template>
  <div class="card">
    <h3>useUrlSearchParams</h3>
    <p>当前查询参数: {{ JSON.stringify(params) }}</p>
    <button @click="setSearchParam">设置 search=VueUse</button>
    <button @click="params.page = 1">设置 page=1</button>
  </div>
</template>

🌐 5. createGlobalState - 创建全局状态

<script setup>
import { createGlobalState, useStorage } from '@vueuse/core'

// 创建全局状态
const useGlobalCounter = createGlobalState(
  () => useStorage('global-counter', 0)
)

const counter = useGlobalCounter()
</script>

<template>
  <div class="card">
    <h3>createGlobalState</h3>
    <p>全局计数器: {{ counter }}</p>
    <button @click="counter++">增加</button>
    <button @click="counter--">减少</button>
  </div>
</template>

📋 6. useClipboard - 剪贴板操作

<script setup>
import { ref } from 'vue'
import { useClipboard } from '@vueuse/core'

const source = ref('复制这段文本')
const { copy, copied } = useClipboard()

const handleCopy = () => {
  copy(source.value)
}
</script>

<template>
  <div class="card">
    <h3>useClipboard</h3>
    <p>文本内容: {{ source }}</p>
    <button @click="handleCopy">
      {{ copied ? '已复制!' : '复制到剪贴板' }}
    </button>
  </div>
</template>

📏 7. useElementBounding - 获取元素边界信息

<script setup>
import { ref } from 'vue'
import { useElementBounding } from '@vueuse/core'

const el = ref(null)
const rect = useElementBounding(el)

// 响应窗口大小变化
const { width, height, top, left, bottom, right } = rect
</script>

<template>
  <div class="card">
    <h3>useElementBounding</h3>
    <div ref="el" class="bounding-box">
      调整窗口大小查看变化
    </div>
    <div class="bounding-info">
      <p>宽度: {{ width.toFixed(0) }}px</p>
      <p>高度: {{ height.toFixed(0) }}px</p>
      <p>顶部: {{ top.toFixed(0) }}px</p>
      <p>左侧: {{ left.toFixed(0) }}px</p>
    </div>
  </div>
</template>

<style>
.bounding-box {
  width: 100%;
  height: 120px;
  background: var(--color-primary);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  margin-bottom: 16px;
}

.bounding-info {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
</style>

🖱️ 8. vOnClickOutside - 点击外部事件

<script setup>
import { vOnClickOutside } from '@vueuse/components'
import { ref } from 'vue'

const showModal = ref(false)
const modal = ref(null)

const openModal = () => showModal.value = true
const closeModal = () => showModal.value = false
</script>

<template>
  <div class="card">
    <h3>vOnClickOutside</h3>
    <button @click="openModal">打开弹窗</button>
    
    <div v-if="showModal" ref="modal" v-on-click-outside="closeModal" class="modal">
      <p>点击弹窗外部区域关闭</p>
      <button @click="closeModal">关闭弹窗</button>
    </div>
  </div>
</template>

<style>
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: white;
  padding: 24px;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  z-index: 100;
}
</style>

⏳ 9. useDebounceFn - 防抖函数

<script setup>
import { ref } from 'vue'
import { useDebounceFn } from '@vueuse/core'

const searchQuery = ref('')
const results = ref([])

// 创建防抖函数
const debouncedSearch = useDebounceFn(() => {
  results.value = [`结果: ${searchQuery.value}`, `相关: ${searchQuery.value}-1`, `相关: ${searchQuery.value}-2`]
}, 500)

// 监听输入变化
watch(searchQuery, debouncedSearch)
</script>

<template>
  <div class="card">
    <h3>useDebounceFn</h3>
    <input v-model="searchQuery" placeholder="输入搜索内容..." />
    <ul v-if="results.length">
      <li v-for="(result, index) in results" :key="index">{{ result }}</li>
    </ul>
    <p v-else>输入内容开始搜索 (500ms防抖)</p>
  </div>
</template>

📐 10. useElementSize - 获取元素尺寸

<script setup>
import { ref } from 'vue'
import { useElementSize } from '@vueuse/core'

const el = ref(null)
const { width, height } = useElementSize(el)
</script>

<template>
  <div class="card">
    <h3>useElementSize</h3>
    <div ref="el" class="resizable-box">
      <p>调整此元素大小</p>
      <p>宽度: {{ width }}px</p>
      <p>高度: {{ height }}px</p>
    </div>
  </div>
</template>

<style>
.resizable-box {
  resize: both;
  overflow: auto;
  background: var(--color-primary);
  color: white;
  padding: 16px;
  border-radius: 8px;
  min-height: 100px;
  min-width: 150px;
}
</style>

🧬 11. useMutationObserver - DOM 变化观察

<script setup>
import { ref } from 'vue'
import { useMutationObserver } from '@vueuse/core'

const el = ref(null)
const messages = ref([])

useMutationObserver(
  el,
  (mutations) => {
    mutations.forEach(mutation => {
      messages.value.push(`DOM 变化类型: ${mutation.type}`)
    })
  },
  { childList: true, subtree: true }
)

const addElement = () => {
  const newEl = document.createElement('div')
  newEl.textContent = '新元素 ' + (el.value.children.length + 1)
  el.value.appendChild(newEl)
}
</script>

<template>
  <div class="card">
    <h3>useMutationObserver</h3>
    <div ref="el" class="mutation-container">
      <div>初始元素</div>
    </div>
    <button @click="addElement">添加元素</button>
    <div class="mutation-log">
      <h4>DOM 变化日志:</h4>
      <ul>
        <li v-for="(msg, index) in messages" :key="index">{{ msg }}</li>
      </ul>
    </div>
  </div>
</template>

<style>
.mutation-container {
  border: 1px solid #ddd;
  padding: 16px;
  margin-bottom: 16px;
  min-height: 50px;
}

.mutation-log {
  max-height: 150px;
  overflow-y: auto;
  border: 1px solid #eee;
  padding: 8px;
  margin-top: 16px;
}
</style>

💉 12. createInjectionState - 创建注入状态

<script setup>
// 在共享状态文件中
import { createInjectionState } from '@vueuse/core'

const [useProviderCounter, useConsumerCounter] = createInjectionState(
  (initialValue) => {
    const count = ref(initialValue)
    const increment = () => count.value++
    return { count, increment }
  }
)

// 在父组件中
import { useProviderCounter } from './counterState'

useProviderCounter(0)

// 在子组件中
import { useConsumerCounter } from './counterState'

const { count, increment } = useConsumerCounter()
</script>

<template>
  <div class="card">
    <h3>createInjectionState</h3>
    <p>此功能需在多个组件间使用,请查看代码示例</p>
  </div>
</template>

🔄 13. useToggle - 切换布尔值

<script setup>
import { useToggle } from '@vueuse/core'

const [value, toggle] = useToggle(false)
</script>

<template>
  <div class="card">
    <h3>useToggle</h3>
    <p>当前状态: {{ value ? '开启' : '关闭' }}</p>
    <button @click="toggle()">切换</button>
    <button @click="toggle(true)">设置为开启</button>
    <button @click="toggle(false)">设置为关闭</button>
  </div>
</template>

🔍 14. useResizeObserver - 监听元素尺寸变化

<script setup>
import { ref } from 'vue'
import { useResizeObserver } from '@vueuse/core'

const el = ref(null)
const width = ref(0)
const height = ref(0)

useResizeObserver(el, (entries) => {
  const entry = entries[0]
  width.value = entry.contentRect.width
  height.value = entry.contentRect.height
})
</script>

<template>
  <div class="card">
    <h3>useResizeObserver</h3>
    <div ref="el" class="resizable-box">
      <p>调整此元素大小</p>
      <p>宽度: {{ width }}px</p>
      <p>高度: {{ height }}px</p>
    </div>
  </div>
</template>

<style>
.resizable-box {
  resize: both;
  overflow: auto;
  background: var(--color-primary);
  color: white;
  padding: 16px;
  border-radius: 8px;
  min-height: 100px;
  min-width: 150px;
}
</style>

15. useMouse - 跟踪鼠标位置

<script setup>
import { useMouse } from '@vueuse/core'

const { x, y, sourceType } = useMouse()
</script>

<template>
  <div class="card">
    <h3>useMouse</h3>
    <p>鼠标位置: ({{ x }}, {{ y }})</p>
    <p>来源: {{ sourceType }}</p>
  </div>
</template>

💾 状态持久化

useLocalStorageuseSessionStorage

  • ​使用场景​​:

    • 用户偏好设置(主题、字体大小)持久化
    • 表单草稿自动保存
    • 跨页面状态共享(如购物车)
  • ​优势​​:自动同步存储与内存状态,支持 TypeScript 类型声明

<script setup lang="ts">
import { useLocalStorage } from '@vueuse/core'

// 持久化存储用户主题设置(自动同步到 localStorage)
const theme = useLocalStorage('user-theme', 'light', {
  serializer: {
    parse: (val) => val || 'light',
    serialize: (val) => val
  }
})

// 临时存储表单草稿(仅当前会话有效)
const formDraft = useSessionStorage('form-draft', {
  username: '',
  email: ''
})
</script>

<template>
  <div class="card">
    <h3>useLocalStorage & useSessionStorage</h3>
    <div class="setting-item">
      <label>主题模式:</label>
      <select v-model="theme">
        <option value="light">浅色</option>
        <option value="dark">深色</option>
      </select>
    </div>
    <div class="draft-info">
      <p>表单草稿:{{ formDraft }}</p>
      <button @click="formDraft.username = '新用户'">修改草稿</button>
    </div>
  </div>
</template>

<style>
.setting-item {
  margin: 16px 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

.draft-info {
  margin-top: 20px;
  padding: 12px;
  background: #f5f7fa;
  border-radius: 8px;
}
</style>

🌐 网络请求

useFetch

  • ​使用场景​​:

    • 数据列表加载(分页/筛选)
    • 接口数据缓存
    • 实时数据轮询(结合 refetchInterval
  • ​优势​​:内置加载状态、错误处理、自动重试,支持响应式数据更新

<script setup>
import { useFetch } from '@vueuse/core'

// 获取用户列表(自动缓存,30秒自动刷新)
const { data: users, isLoading, error, refetch } = useFetch('https://api.example.com/users', {
  refetchInterval: 30000, // 30秒自动刷新
  immediate: true, // 立即执行请求
  onResponse: (response) => {
    // 转换数据结构
    return { data: response.data.map(user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` })) }
  }
})
</script>

<template>
  <div class="card">
    <h3>useFetch</h3>
    <div class="control">
      <button @click="refetch" :disabled="isLoading">
        {{ isLoading ? '加载中...' : '手动刷新' }}
      </button>
    </div>
    <div v-if="error" class="error">{{ error.message }}</div>
    <ul v-else-if="users" class="user-list">
      <li v-for="user in users.data" :key="user.id">
        {{ user.fullName }} - {{ user.email }}
      </li>
    </ul>
  </div>
</template>

<style>
.user-list {
  list-style: none;
  padding: 0;
  margin: 16px 0;
}

.user-list li {
  padding: 8px;
  border-bottom: 1px solid #eee;
}
</style>

⏱️ 响应式定时器

useInterval

  • ​使用场景​​:

    • 倒计时组件(如验证码发送)
    • 实时数据刷新(如实时行情)
    • 动画帧控制
  • ​优势​​:自动响应式暂停/恢复,支持动态调整间隔时间

<script setup>
import { ref } from 'vue'
import { useInterval } from '@vueuse/core'

const count = ref(0)
const remaining = ref(60) // 验证码倒计时60秒

// 基础计数器(每1秒递增)
useInterval(() => {
  count.value++
}, 1000)

// 带条件的倒计时(剩余时间>0时执行)
useInterval(() => {
  if (remaining.value > 0) {
    remaining.value--
  }
}, 1000, { immediate: true })
</script>

<template>
  <div class="card">
    <h3>useInterval</h3>
    <p>基础计数器:{{ count }}</p>
    <p class="countdown">验证码剩余时间:{{ remaining }}秒</p>
    <button @click="remaining = 60">重置倒计时</button>
  </div>
</template>

<style>
.countdown {
  color: #ff4d4f;
  font-weight: bold;
  margin: 12px 0;
}
</style>

🚦 条件执行

useThrottle

  • ​使用场景​​:

    • 滚动事件处理(如无限滚动加载)
    • 窗口 resize 事件优化
    • 高频点击按钮限制
  • ​优势​​:精确控制执行频率,支持 trailing/leading 模式

<script setup>
import { ref } from 'vue'
import { useThrottle } from '@vueuse/core'

const scrollCount = ref(0)
const throttledScroll = useThrottle(() => {
  scrollCount.value++
}, 500, { trailing: true }) // 每500ms最多执行一次,触发结束后执行

// 监听窗口滚动
window.addEventListener('scroll', throttledScroll)
</script>

<template>
  <div class="card" style="height: 300px; overflow: auto;">
    <div style="height: 1000px; display: flex; align-items: center; justify-content: center;">
      <p>滚动测试区域</p>
      <p>滚动次数(500ms节流):{{ scrollCount }}</p>
    </div>
  </div>
</template>

🔐 浏览器权限

usePermission

  • ​使用场景​​:

    • 检查摄像头/麦克风访问权限
    • 地理位置权限状态提示
    • 通知权限请求引导
  • ​优势​​:自动监听权限变更,返回标准化状态(granted/denied/prompt)

<script setup>
import { ref } from 'vue'
import { usePermission } from '@vueuse/core'

// 检查地理位置权限
const geolocation = usePermission('geolocation')
// 检查通知权限
const notification = usePermission('notifications')
</script>

<template>
  <div class="card">
    <h3>usePermission</h3>
    <div class="permission-item">
      <h4>地理位置权限:</h4>
      <p v-if="geolocation === 'granted'">已授权</p>
      <p v-else-if="geolocation === 'denied'">已拒绝</p>
      <p v-else>未询问(点击按钮请求权限)</p>
      <button @click="navigator.geolocation.getCurrentPosition(() => {}, () => {})">
        请求地理位置权限
      </button>
    </div>
    <div class="permission-item">
      <h4>通知权限:</h4>
      <p v-if="notification === 'granted'">已授权</p>
      <p v-else-if="notification === 'denied'">已拒绝</p>
      <p v-else>未询问(点击按钮请求权限)</p>
      <button @click="Notification.requestPermission()">
        请求通知权限
      </button>
    </div>
  </div>
</template>

<style>
.permission-item {
  margin: 16px 0;
  padding: 12px;
  border: 1px solid #eee;
  border-radius: 8px;
}

.permission-item button {
  margin-top: 8px;
  padding: 6px 12px;
}
</style>

📡 实时通信

useWebSocket

  • ​使用场景​​:

    • 实时聊天应用
    • 股票行情推送
    • 设备状态监控
  • ​优势​​:自动重连、消息队列管理、支持二进制/文本数据

<script setup>
import { ref } from 'vue'
import { useWebSocket } from '@vueuse/core'

const messages = ref([])
const inputText = ref('')

// 连接 WebSocket 服务(示例地址)
const { send, status } = useWebSocket('wss://echo.websocket.org', {
  onMessage: (event) => {
    messages.value.push(`收到:${event.data}`)
  },
  autoReconnect: true, // 自动重连
  reconnectInterval: 3000, // 3秒重连间隔
  heartbeat: {
    message: 'ping',
    interval: 5000 // 5秒发送心跳
  }
})
</script>

<template>
  <div class="card">
    <h3>useWebSocket</h3>
    <div class="status">连接状态:{{ status }}</div>
    <div class="message-log">
      <div v-for="(msg, index) in messages" :key="index" class="message">
        {{ msg }}
      </div>
    </div>
    <div class="input-area">
      <input v-model="inputText" placeholder="输入消息..." />
      <button @click="send(inputText); inputText = ''">发送</button>
    </div>
  </div>
</template>

<style>
.status {
  padding: 8px;
  background: #e6f7ff;
  border-radius: 4px;
  margin-bottom: 12px;
}

.message-log {
  height: 200px;
  overflow-y: auto;
  margin: 12px 0;
  padding: 8px;
  background: #f9f9f9;
  border-radius: 4px;
}

.message {
  padding: 4px 0;
  border-bottom: 1px solid #eee;
}

.input-area {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.input-area input {
  flex: 1;
  padding: 6px 12px;
}
</style>

🎨 主题模式

useDark

  • ​使用场景​​:

    • 系统级暗模式自动跟随
    • 手动切换主题模式
    • 主题状态持久化
  • ​优势​​:自动检测系统颜色方案,支持手动覆盖,提供 CSS 变量注入

<script setup>
import { useDark, useToggle } from '@vueuse/core'

// 检测系统暗模式,支持手动切换
const isDark = useDark({
  selector: 'body',
  attribute: 'data-theme',
  valueDark: 'dark',
  valueLight: 'light'
})
const toggleDark = useToggle(isDark)
</script>

<template>
  <div class="card">
    <h3>useDark</h3>
    <p>当前主题:{{ isDark ? '暗模式' : '亮模式' }}</p>
    <button @click="toggleDark()">切换主题</button>
    <div class="theme-preview">
      <div class="light-box">亮模式预览</div>
      <div class="dark-box">暗模式预览</div>
    </div>
  </div>
</template>

<style>
/* 配合 useDark 的 CSS 变量 */
:root {
  --color-background: #ffffff;
  --color-text: #333333;
}

[data-theme="dark"] {
  --color-background: #1a1a1a;
  --color-text: #ffffff;
}

.theme-preview {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 16px;
}

.light-box, .dark-box {
  padding: 20px;
  border-radius: 8px;
  color: var(--color-text);
  background: var(--color-background);
}

.dark-box {
  opacity: 0.8;
}
</style>

🌍 语言检测

usePreferredLanguages

  • ​使用场景​​:

    • 国际化应用语言自动匹配
    • 内容本地化展示(如日期/数字格式)
    • 用户语言偏好统计
  • ​优势​​:返回用户浏览器/系统的首选语言列表(按优先级排序)

<script setup>
import { ref, watchEffect } from 'vue'
import { usePreferredLanguages } from '@vueuse/core'

const languages = ref<string[]>([])
const supportedLangs = ['zh-CN', 'en-US', 'ja-JP']

// 监听语言变化
watchEffect(() => {
  languages.value = usePreferredLanguages()
})
</script>

<template>
  <div class="card">
    <h3>usePreferredLanguages</h3>
    <p>系统首选语言:</p>
    <ul>
      <li v-for="(lang, index) in languages" :key="index">
        {{ lang }} {{ supportedLangs.includes(lang) ? '(支持)' : '(不支持)' }}
      </li>
    </ul>
    <p class="current-lang">当前应用语言:zh-CN</p>
  </div>
</template>

<style>
ul {
  list-style: none;
  padding: 0;
  margin: 12px 0;
}

li {
  padding: 4px 0;
  color: #666;
}
</style>

🧩 注入状态

createInjectionState

  • ​使用场景​​:

    • 组件库状态管理(如表格配置、表单校验)
    • 跨层级组件状态共享(避免 Prop drilling)
    • 插件系统状态隔离
  • ​优势​​:提供 Provider/Consumer 模式,支持响应式状态注入

<!-- 父组件:StateProvider.vue -->
<script setup>
import { createInjectionState } from '@vueuse/core'

// 定义状态工厂函数(返回状态和方法)
const createState = () => {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }
}

// 创建注入状态(返回 Provider 和 Consumer)
const [useStateProvider, useStateConsumer] = createInjectionState(createState)

// 在当前组件树提供状态
useStateProvider()
</script>

<template>
  <div class="card">
    <h3>createInjectionState(Provider)</h3>
    <button @click="useStateProvider().increment">父组件增加计数</button>
    <ChildComponent />
  </div>
</template>

<!-- 子组件:ChildComponent.vue -->
<script setup>
import { useStateConsumer } from './StateProvider.vue'

const { count, increment } = useStateConsumer()
</script>

<template>
  <div class="child-card">
    <h4>子组件状态</h4>
    <p>当前计数:{{ count }}</p>
    <button @click="increment">子组件增加计数</button>
  </div>
</template>

<style>
.child-card {
  margin-top: 16px;
  padding: 12px;
  border: 1px solid #eee;
  border-radius: 8px;
}
</style>

总结

VueUse 提供了大量实用的 Composition API,可以显著提高开发效率。本文介绍了 15 个最常用的 hooks:

  1. useNow - 实时获取当前时间
  2. useVModel - 简化 v-model 绑定
  3. useElementVisibility - 检测元素可见性
  4. useUrlSearchParams - 操作 URL 查询参数
  5. createGlobalState - 创建全局状态
  6. useClipboard - 剪贴板操作
  7. useElementBounding - 获取元素边界信息
  8. vOnClickOutside - 点击外部事件指令
  9. useDebounceFn - 防抖函数
  10. useElementSize - 获取元素尺寸
  11. useMutationObserver - DOM 变化观察
  12. createInjectionState - 创建注入状态
  13. useToggle - 切换布尔值
  14. useResizeObserver - 监听元素尺寸变化
  15. useMouse - 跟踪鼠标位置
  16. ​useLocalStorage/useSessionStorage​​ - 本地/会话存储持久化
  17. ​useFetch​​ - 响应式网络请求
  18. ​useInterval​​ - 响应式定时器
  19. ​useThrottle​​ - 函数节流控制
  20. ​usePermission​​ - 浏览器权限检测
  21. ​useWebSocket​​ - 实时 WebSocket 通信
  22. ​useDark​​ - 系统暗模式检测与切换
  23. ​usePreferredLanguages​​ - 系统首选语言检测
  24. ​createInjectionState​​ - 组件注入状态管理

这些 Hooks 进一步扩展了 VueUse 的能力边界,结合官方文档(vueuse.org)可探索更多实用工具,根据具体业务场景灵活选用,大幅提升开发效率。