主题
概述
VueUse 是 Vue 3 的组合式工具函数集合,提供了 200+ 实用的 composables,涵盖状态管理、浏览器 API、传感器、动画等多个领域。
已配置自动导入
项目已配置 @vueuse/core 自动导入,大部分函数无需手动 import。
但 @vueuse/router 必须手动导入。
常用工具
useStorage - 持久化存储
持久化状态
typescript
import { useStorage, StorageSerializers } from '@vueuse/core'
// 简单类型自动序列化
const theme = useStorage<'light' | 'dark'>('theme', 'light')
const count = useStorage('count', 0)
// 复杂类型需要 serializer
const user = useStorage<User | null>(
'user',
null,
undefined,
{ serializer: StorageSerializers.object }
)
// 使用 sessionStorage
const sessionData = useStorage('data', {}, sessionStorage)refDebounced - 防抖 Ref
防抖搜索
typescript
import { refDebounced } from '@vueuse/core'
const keyword = ref('')
const debouncedKeyword = refDebounced(keyword, 500)
// 在 TanStack Query 中使用
const { data } = useQuery({
queryKey: ['search', debouncedKeyword], // 使用防抖后的值
queryFn: () => searchApi.search(debouncedKeyword.value),
})注意事项
refDebounced 接收的是 Ref 对象,不是函数。
typescript
// ✅ 正确
const debouncedKeyword = refDebounced(keyword, 500)
// ❌ 错误 - 这是 useDebounceFn 的用法
const debouncedFn = useDebounceFn(() => search(), 500)useDebounceFn / useThrottleFn - 防抖/节流函数
防抖/节流函数
typescript
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
// 防抖函数
const debouncedSearch = useDebounceFn((keyword: string) => {
console.log('搜索:', keyword)
}, 500)
// 节流函数
const throttledScroll = useThrottleFn(() => {
console.log('滚动处理')
}, 200)useWindowSize - 窗口尺寸
响应式布局
typescript
import { useWindowSize } from '@vueuse/core'
const { width, height } = useWindowSize()
const isMobile = computed(() => width.value < 768)
const isTablet = computed(() => width.value >= 768 && width.value < 1024)
const isDesktop = computed(() => width.value >= 1024)useOnline - 网络状态
网络状态
typescript
import { useOnline } from '@vueuse/core'
const isOnline = useOnline()
watch(isOnline, (online) => {
if (online) {
toast.success('网络已恢复')
} else {
toast.warning('网络已断开')
}
})useClipboard - 剪贴板
复制到剪贴板
vue
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
const { copy, copied } = useClipboard()
const handleCopy = (text: string) => {
copy(text)
}
</script>
<template>
<Button @click="handleCopy('要复制的内容')">
{{ copied ? '已复制' : '复制' }}
</Button>
</template>useDark - 暗色模式
暗色模式切换
typescript
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
const toggleDark = useToggle(isDark)
// 使用
toggleDark() // 切换
isDark.value = true // 直接设置useEventListener - 事件监听
自动清理事件
typescript
import { useEventListener } from '@vueuse/core'
// 组件卸载时自动移除
useEventListener(window, 'resize', () => {
console.log('窗口大小变化')
})
useEventListener(document, 'keydown', (e) => {
if (e.key === 'Escape') {
closeModal()
}
})useIntersectionObserver - 元素可见性
懒加载/无限滚动
vue
<script setup lang="ts">
import { useIntersectionObserver } from '@vueuse/core'
const loadMoreRef = ref<HTMLElement | null>(null)
useIntersectionObserver(loadMoreRef, ([{ isIntersecting }]) => {
if (isIntersecting) {
loadMore()
}
})
</script>
<template>
<div>
<DataList :items="items" />
<div ref="loadMoreRef">加载更多...</div>
</div>
</template>@vueuse/router
必须手动导入
@vueuse/router 的函数不会自动导入,必须显式 import。
useRouteParams - 响应式路由参数
响应式路由参数
typescript
// 必须显式导入!
import { useRouteParams, useRouteQuery } from '@vueuse/router'
// 从 /articles/:id 获取 id
const id = useRouteParams<string>('id')
// 参数变化自动触发重新请求
const { data } = useQuery({
queryKey: ['article', id],
queryFn: () => articleApi.getById(id.value),
})useRouteQuery - 响应式查询参数
响应式查询参数
typescript
import { useRouteQuery } from '@vueuse/router'
// 从 /search?keyword=xxx 获取
const keyword = useRouteQuery<string>('keyword', '')
const page = useRouteQuery<string>('page', '1')
// 修改会自动同步到 URL
keyword.value = 'vue3' // URL 变为 ?keyword=vue3
// 适合需要分享链接的场景
</script>
<template>
<Input v-model="keyword" placeholder="搜索..." />
</template>何时使用 useRouteQuery?
| 场景 | 方案 |
|---|---|
| 需要分享搜索结果链接 | useRouteQuery |
| 需要浏览器后退保持状态 | useRouteQuery |
| 普通分页/筛选 | 直接用 ref |
普通分页用 ref
vue
<script setup lang="ts">
// ✅ 普通分页用 ref 即可,无需同步到 URL
const page = ref(1)
const pageSize = ref(10)
const { data } = useArticles({ page, pageSize })
</script>完整示例
搜索页面
vue
<script setup lang="ts">
import { useRouteQuery } from '@vueuse/router'
import { refDebounced } from '@vueuse/core'
// 搜索关键词同步到 URL
const keyword = useRouteQuery<string>('keyword', '')
const debouncedKeyword = refDebounced(keyword, 500)
// 分页不同步到 URL
const page = ref(1)
const pageSize = ref(10)
const { data, isLoading } = useQuery({
queryKey: ['search', debouncedKeyword, page, pageSize],
queryFn: () => searchApi.search({
keyword: debouncedKeyword.value,
page: page.value,
page_size: pageSize.value,
}),
})
// 关键词变化时重置页码
watch(debouncedKeyword, () => {
page.value = 1
})
</script>
<template>
<div>
<Input v-model="keyword" placeholder="搜索..." />
<div v-if="isLoading">加载中...</div>
<ResultList v-else :items="data?.items" />
<Pagination v-model:page="page" :total="data?.total" />
</div>
</template>