VueUse简介
VueUse是一个Vue组合式函数(Composables)的集合,为Vue开发者提供了大量实用的工具函数,可以极大地提高开发效率。
- VueUse是一个Vue组合式函数的集合
- 提供了大量可重用的逻辑
- 适用于Vue 2和Vue 3
- 无需安装整个库,可以按需导入
- 完全使用TypeScript编写,提供优秀的类型支持
安装VueUse
# 使用npm
npm i @vueuse/core
# 使用yarn
yarn add @vueuse/core
# 使用pnpm
pnpm add @vueuse/core
使用VueUse:
javascript
// 按需导入
import { useMouse, useLocalStorage } from '@vueuse/core'
export default {
setup() {
// 使用组合式函数
const { x, y } = useMouse()
const storage = useLocalStorage('my-key', 'default-value')
return {
x, y,
storage
}
}
}
VueUse生态系统:
javascript
@vueuse/core # 核心库,包含最常用的组合式函数
@vueuse/components # 组件库
@vueuse/motion # 动画相关
@vueuse/rxjs # RxJS集成
@vueuse/firebase # Firebase集成
@vueuse/math # 数学相关工具
@vueuse/sound # 声音相关工具
@vueuse/router # Vue Router集成
@vueuse/head # 标题和元信息管理
@vueuse/integrations #为工具库提供集成封装器
浏览器API相关的Composables
useWindowScroll - 监听窗口滚动
javascript
import { useWindowScroll } from '@vueuse/core'
export default {
setup() {
const { x, y, isScrolling, arrivedState, directions } = useWindowScroll()
console.log(x.value) // 水平滚动位置
console.log(y.value) // 垂直滚动位置
console.log(isScrolling.value) // 是否正在滚动
console.log(arrivedState) // { top: true, bottom: false, left: true, right: false }
console.log(directions) // { top: false, bottom: true, left: false, right: false }
return {
x, y, isScrolling,
// 可以使用计算属性创建逻辑
isAtBottom: computed(() => arrivedState.bottom)
}
}
}
useWindowSize - 监听窗口大小
javascript
import { useWindowSize } from '@vueuse/core'
export default {
setup() {
const { width, height } = useWindowSize()
console.log(width.value) // 窗口宽度
console.log(height.value) // 窗口高度
// 响应式逻辑
const isMobile = computed(() => width.value < 768)
const isTablet = computed(() => width.value >= 768 && width.value < 1024)
const isDesktop = computed(() => width.value >= 1024)
return {
width, height,
isMobile, isTablet, isDesktop
}
}
}
useDark - 暗黑模式切换
javascript
import { useDark, useToggle } from '@vueuse/core'
export default {
setup() {
// 使用暗黑模式
const isDark = useDark()
// 创建切换函数
const toggleDark = useToggle(isDark)
return {
isDark,
toggleDark
}
}
}
useClipboard - 剪贴板操作
javascript
import { useClipboard } from '@vueuse/core'
export default {
setup() {
const source = ref('Hello VueUse!')
const { copy, copied, isSupported } = useClipboard({ source })
// 手动复制
const copyToClipboard = (text) => {
source.value = text
copy()
}
return {
source,
copy,
copied,
isSupported,
copyToClipboard
}
}
}
useLocalStorage - 本地存储
javascript
import { useLocalStorage } from '@vueuse/core'
export default {
setup() {
// 基本用法
const storedValue = useLocalStorage('my-key', 'default-value')
// 存储对象
const user = useLocalStorage('user', {
name: '',
email: '',
preferences: {
theme: 'light',
language: 'en'
}
})
// 监听变化
watch(
() => user.value,
(newValue) => {
console.log('用户数据已更新:', newValue)
},
{ deep: true }
)
return {
storedValue,
user
}
}
}
useGeolocation - 地理位置API
javascript
import { useGeolocation } from '@vueuse/core'
export default {
setup() {
const {
coords,
locatedAt,
error,
pause,
resume,
isLoading
} = useGeolocation()
// coords包含经纬度信息
console.log(coords.value.latitude) // 纬度
console.log(coords.value.longitude) // 经度
return {
coords,
locatedAt,
error,
isLoading,
pause, // 暂停位置更新
resume // 恢复位置更新
}
}
}
useFullscreen - 全屏API
javascript
import { useFullscreen } from '@vueuse/core'
export default {
setup() {
const { isFullscreen, enter, exit, toggle } = useFullscreen()
// 也可以指定元素
const el = ref()
const { isFullscreen: elFullscreen } = useFullscreen(el)
return {
isFullscreen,
elFullscreen,
enter,
exit,
toggle,
el
}
}
}
状态管理相关的Composables
useStorage - 通用存储API
javascript
import { useStorage } from '@vueuse/core'
export default {
setup() {
// 使用localStorage (默认)
const localData = useStorage('local-key', { foo: 'bar' })
// 使用sessionStorage
const sessionData = useStorage('session-key', { foo: 'bar' }, sessionStorage)
// 自定义存储实现
const customStorage = {
getItem: (key) => myStorageSystem.get(key),
setItem: (key, value) => myStorageSystem.set(key, value)
}
const customData = useStorage('custom-key', { foo: 'bar' }, customStorage)
return {
localData,
sessionData,
customData
}
}
}
useTimeoutFn - 延时执行函数
javascript
import { useTimeoutFn } from '@vueuse/core'
export default {
setup() {
const {
start,
stop,
isPending
} = useTimeoutFn(() => {
console.log('3秒后执行')
}, 3000)
// 立即启动
start()
return {
start,
stop,
isPending
}
}
}
useIntervalFn - 定时执行函数
javascript
import { useIntervalFn } from '@vueuse/core'
export default {
setup() {
const count = ref(0)
const {
pause,
resume,
isActive
} = useIntervalFn(() => {
count.value++
console.log(`计数: ${count.value}`)
}, 1000)
return {
count,
pause,
resume,
isActive
}
}
}
useDebounceFn - 防抖函数
javascript
import { useDebounceFn } from '@vueuse/core'
export default {
setup() {
const searchQuery = ref('')
const results = ref([])
// 创建防抖函数,延迟500ms
const debouncedSearch = useDebounceFn(() => {
console.log('执行搜索:', searchQuery.value)
// 模拟API请求
fetch(`/api/search?q=${searchQuery.value}`)
.then(res => res.json())
.then(data => {
results.value = data
})
}, 500)
// 监听搜索词变化
watch(searchQuery, () => {
if (searchQuery.value.trim()) {
debouncedSearch()
}
})
return {
searchQuery,
results
}
}
}
useThrottleFn - 节流函数
javascript
import { useThrottleFn } from '@vueuse/core'
export default {
setup() {
const position = ref({ x: 0, y: 0 })
// 创建节流函数,每200ms最多执行一次
const throttledMouseMove = useThrottleFn((event) => {
position.value = { x: event.clientX, y: event.clientY }
console.log('鼠标位置:', position.value)
}, 200)
const handleMouseMove = (event) => {
throttledMouseMove(event)
}
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove)
})
onUnmounted(() => {
window.removeEventListener('mousemove', handleMouseMove)
})
return {
position
}
}
}
用户交互相关的Composables
useMouse - 鼠标位置追踪
javascript
import { useMouse } from '@vueuse/core'
export default {
setup() {
// 基本用法
const { x, y, sourceType } = useMouse()
// 相对于特定元素
const el = ref()
const { x: elX, y: elY } = useMouse({ target: el })
return {
x, y, sourceType,
elX, elY,
el
}
}
}
usePointer - 指针位置追踪(支持触摸)
javascript
import { usePointer } from '@vueuse/core'
export default {
setup() {
const {
x,
y,
pressure,
pointerType,
isInside
} = usePointer()
// 检测设备类型
const isMouse = computed(() => pointerType.value === 'mouse')
const isTouch = computed(() => pointerType.value === 'touch')
const isPen = computed(() => pointerType.value === 'pen')
return {
x, y, pressure, pointerType, isInside,
isMouse, isTouch, isPen
}
}
}
useKeyboard - 键盘事件监听
javascript
import { useKeyboard } from '@vueuse/core'
export default {
setup() {
// 监听特定按键
const pressed = useKeyboard('Enter')
// 监听多个按键
const ctrlPressed = useKeyboard('Control')
const spacePressed = useKeyboard(' ')
// 组合键
const ctrlAPressed = useKeyboard('Control+a')
const ctrlSPressed = useKeyboard('Control+s')
// 处理组合键
watch(ctrlSPressed, (newValue) => {
if (newValue) {
console.log('Ctrl+S 被按下,执行保存操作')
// 保存操作
}
})
return {
pressed,
ctrlPressed,
spacePressed,
ctrlAPressed,
ctrlSPressed
}
}
}
useDraggable - 拖拽功能
javascript
import { useDraggable } from '@vueuse/core'
export default {
setup() {
// 基本用法
const el = ref()
const { x, y, style } = useDraggable(el)
// 限制拖拽范围
const container = ref()
const {
x: boundedX,
y: boundedY,
style: boundedStyle
} = useDraggable(el, {
containerElement: container,
initialValue: { x: 100, y: 100 }
})
// 只允许水平拖拽
const {
x: horizontalX,
style: horizontalStyle
} = useDraggable(el, {
axis: 'x'
})
return {
el,
x, y, style,
container,
boundedX, boundedY, boundedStyle,
horizontalX, horizontalStyle
}
}
}
useDropZone - 拖放区域
javascript
import { useDropZone } from '@vueuse/core'
export default {
setup() {
const dropZoneRef = ref()
const files = ref([])
const { isOverDropZone } = useDropZone(dropZoneRef, {
onDrop,
onOver,
onLeave,
})
function onDrop(files) {
console.log('拖放的文件:', files)
files.value = Array.from(files)
}
function onOver(files) {
console.log('文件进入拖放区域:', files)
}
function onLeave(files) {
console.log('文件离开拖放区域:', files)
}
return {
dropZoneRef,
files,
isOverDropZone
}
}
}
动画与过渡相关的Composables
useTransition - 过渡值
javascript
import { useTransition } from '@vueuse/core'
export default {
setup() {
const source = ref(0)
const output = useTransition(source)
// 更新源值,会平滑过渡到目标值
source.value = 100
// 自定义过渡
const customOutput = useTransition(source, {
duration: 1000,
transition: [0.25, 0.1, 0.25, 1] // 贝塞尔曲线
})
return {
source,
output,
customOutput
}
}
}
useTween - 补间动画
javascript
import { useTween } from '@vueuse/core'
export default {
setup() {
// 从0到100的补间动画,持续1秒
const tween = useTween(100, 1000)
console.log(tween.value) // 当前值(0-100)
// 使用回调
const animated = useTween(100, 1000, {
onUpdate: (value) => {
console.log('当前值:', value)
},
onComplete: () => {
console.log('动画完成')
}
})
return {
tween,
animated
}
}
}
useMotion - 运动动画
javascript
import { useMotion } from '@vueuse/core'
import { ref } from 'vue'
export default {
setup() {
const el = ref()
const { apply, stop, instance } = useMotion(el, {
initial: {
opacity: 0,
y: 100
},
enter: {
opacity: 1,
y: 0,
transition: {
duration: 800,
type: 'spring'
}
},
leave: {
opacity: 0,
y: -100,
transition: {
duration: 300,
ease: 'easeIn'
}
}
})
// 手动应用动画
const applyAnimation = () => {
apply('enter')
}
return {
el,
applyAnimation
}
}
}
网络请求相关的Composables
useFetch - HTTP请求
javascript
import { useFetch } from '@vueuse/core'
export default {
setup() {
// GET请求
const { data, error, pending, execute } = useFetch('/api/users')
console.log(data.value) // 响应数据
console.log(error.value) // 错误信息
console.log(pending.value) // 是否正在请求
// POST请求
const { data: postData, execute: postExecute } = useFetch('/api/users').post({
name: 'John Doe',
email: 'john@example.com'
}).json()
// 手动执行请求
const manualRequest = async () => {
await execute()
console.log('请求完成:', data.value)
}
return {
data,
error,
pending,
execute,
postData,
postExecute,
manualRequest
}
}
}
useAsyncState - 异步状态管理
javascript
import { useAsyncState } from '@vueuse/core'
export default {
setup() {
// 基本用法
const { state, isReady, isLoading, error } = useAsyncState(
fetch('https://api.example.com/data').then(res => res.json()),
{ id: 1, name: 'Loading...' } // 初始状态
)
// 使用异步函数
const fetchUserData = async (userId) => {
const response = await fetch(`https://api.example.com/users/${userId}`)
return response.json()
}
const {
state: userState,
execute: fetchUser,
refresh
} = useAsyncState(fetchUserData, null, {
immediate: false // 不立即执行
})
// 获取用户数据
const getUser = (id) => {
fetchUser(1001) // 传入参数
}
return {
state,
isReady,
isLoading,
error,
userState,
getUser,
refresh
}
}
}
时间与日期相关的Composables
useNow - 当前时间
javascript
import { useNow } from '@vueuse/core'
export default {
setup() {
// 基本用法 - 每秒更新一次
const now = useNow()
// 自定义更新间隔
const slowNow = useNow({
interval: 5000 // 每5秒更新一次
})
// 计算属性
const formattedTime = computed(() => {
return new Intl.DateTimeFormat().format(now.value)
})
return {
now,
slowNow,
formattedTime
}
}
}
useDateFormat - 日期格式化
javascript
import { useDateFormat, useNow } from '@vueuse/core'
export default {
setup() {
const now = useNow()
// 基本格式化
const formatted = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss')
// 使用预设格式
const isoFormat = useDateFormat(now, 'iso')
const usFormat = useDateFormat(now, 'MM/DD/YYYY')
// 自定义格式
const customFormat = useDateFormat(now, 'dddd, MMMM Do YYYY')
// 格式化固定日期
const birthday = useDateFormat('1990-01-01', 'YYYY-MM-DD')
return {
formatted,
isoFormat,
usFormat,
customFormat,
birthday
}
}
}
useCountdown - 倒计时
javascript
import { useCountdown } from '@vueuse/core'
export default {
setup() {
// 基本用法 - 从60秒开始倒计时
const {
countdown,
isRunning,
start,
stop,
reset
} = useCountdown(60, {
interval: 1000, // 每秒更新一次
onFinished: () => {
console.log('倒计时结束')
}
})
// 格式化显示
const formattedTime = computed(() => {
const minutes = Math.floor(countdown.value / 60)
const seconds = countdown.value % 60
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
})
return {
countdown,
isRunning,
start,
stop,
reset,
formattedTime
}
}
}
实用工具类Composables
useToggle - 切换布尔值
javascript
import { useToggle } from '@vueuse/core'
export default {
setup() {
// 基本用法
const [value, toggle] = useToggle(true)
// 手动切换
const flip = () => {
toggle()
}
// 直接设置值
const setTrue = () => {
toggle(true)
}
const setFalse = () => {
toggle(false)
}
return {
value,
flip,
setTrue,
setFalse
}
}
}
useArrayFind - 数组查找
javascript
import { useArrayFind } from '@vueuse/core'
export default {
setup() {
const items = ref([
{ id: 1, name: 'Item 1', category: 'A' },
{ id: 2, name: 'Item 2', category: 'B' },
{ id: 3, name: 'Item 3', category: 'A' }
])
// 查找第一个符合条件的项
const {
find,
result: foundItem
} = useArrayFind(items, item => item.category === 'A')
// 手动触发查找
const findFirstItem = () => {
find()
}
// 带默认值
const {
find: findWithDefault,
result: foundItemWithDefault
} = useArrayFind(
items,
item => item.category === 'C',
{ id: -1, name: 'Not Found' }
)
return {
items,
foundItem,
findFirstItem,
foundItemWithDefault
}
}
}
useTitle - 页面标题管理
javascript
import { useTitle } from '@vueuse/core'
export default {
setup() {
// 基本用法
const title = useTitle('My Awesome App')
// 更新标题
const updateTitle = (newTitle) => {
title.value = newTitle
}
// 恢复原始标题
const restoreTitle = () => {
title.value = null
}
return {
title,
updateTitle,
restoreTitle
}
}
}
useFavicon - 网站图标管理
javascript
import { useFavicon } from '@vueuse/core'
export default {
setup() {
// 基本用法
const favicon = useFavicon()
// 设置特定图标
const setLightFavicon = () => {
favicon.value = '/light-favicon.ico'
}
const setDarkFavicon = () => {
favicon.value = '/dark-favicon.ico'
}
// 使用表情符号作为图标
const setEmojiFavicon = () => {
favicon.value = '🚀'
}
return {
favicon,
setLightFavicon,
setDarkFavicon,
setEmojiFavicon
}
}
}
useToggle - 状态切换
javascript
import { useToggle } from '@vueuse/core'
export default {
setup() {
// 切换布尔值
const [isOpen, toggleOpen] = useToggle(false)
// 切换多个状态
const options = ['Option A', 'Option B', 'Option C']
const [currentOption, toggleOption] = useToggle(options)
// 自定义初始值
const [isDark, toggleDark] = useToggle(true)
return {
isOpen,
toggleOpen,
currentOption,
toggleOption,
isDark,
toggleDark
}
}
}
创建自定义Composables
VueUse的设计理念鼓励我们创建自己的可重用组合式函数。下面是一些自定义Composables的例子:
自定义API状态管理
javascript
// composables/useApiState.js
import { ref, computed } from 'vue'
import { useAsyncState } from '@vueuse/core'
export function useApiState(apiCall, initialData = null, options = {}) {
const {
state: data,
isReady,
isLoading,
error,
execute,
refresh
} = useAsyncState(apiCall, initialData, {
resetOnExecute: false,
...options
})
const hasData = computed(() => !!data.value)
const hasError = computed(() => !!error.value)
const errorMessage = computed(() => {
return hasError.value ? error.value.message : ''
})
return {
data,
isReady,
isLoading,
error,
errorMessage,
hasData,
hasError,
execute,
refresh
}
}
// 使用自定义组合式函数
export default {
setup() {
const fetchUsers = async () => {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
const {
data: users,
isLoading,
errorMessage,
hasError,
refresh
} = useApiState(fetchUsers, [])
return {
users,
isLoading,
errorMessage,
hasError,
refresh
}
}
}
自定义验证功能
javascript
// composables/useValidation.js
import { ref, computed } from 'vue'
export function useValidation(rules, initialValue = '') {
const value = ref(initialValue)
const error = ref('')
const isDirty = ref(false)
const validate = () => {
isDirty.value = true
error.value = ''
for (const rule of rules) {
const result = rule(value.value)
if (result !== true) {
error.value = result
return false
}
}
return true
}
const isValid = computed(() => {
if (!isDirty.value) return true
return !error.value
})
// 常用验证规则
const requiredRule = (val) => !!val || '此字段为必填项'
const emailRule = (val) => {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return pattern.test(val) || '请输入有效的邮箱地址'
}
const minLengthRule = (min) => (val) => val.length >= min || `最少需要${min}个字符`
return {
value,
error,
isDirty,
isValid,
validate,
rules: {
required: requiredRule,
email: emailRule,
minLength: minLengthRule
}
}
}
// 使用自定义验证
export default {
setup() {
const {
value: email,
error: emailError,
isDirty: emailDirty,
isValid: emailValid,
validate: validateEmail
} = useValidation([
value => !!value || '邮箱是必填项',
value => {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return pattern.test(value) || '请输入有效的邮箱地址'
}
])
const submitForm = () => {
validateEmail()
if (emailValid.value) {
console.log('表单提交:', email.value)
}
}
return {
email,
emailError,
emailDirty,
emailValid,
submitForm
}
}
}
集成
- useAsyncValidator
- useAxios
- useChangeCase
- useCookies
- useDrauu
- useFocusTrap
- useFuse
- useIDBKeyval
- useJwt
- useNProgress
- useQRCode
- useSortable
VueUse最佳实践
-
按需导入:只导入需要的功能,减少打包体积
// ✅ 推荐 - 按需导入 import { useMouse } from '@vueuse/core' // ❌ 不推荐 - 导入整个库 import VueUse from '@vueuse/core' -
合理使用响应式:注意哪些返回值是响应式的,哪些不是
javascriptconst { x, y } = useMouse() // x和y都是响应式的ref const source = ref('text') // source是响应式的ref -
清理资源:某些Composables需要手动清理资源
javascriptconst { pause, resume } = useIntervalFn(callback, 1000) onUnmounted(() => { pause() // 清理定时器 }) -
组合使用:将多个Composables组合使用,创建更复杂的功能
javascriptconst { x, y } = useMouse() const isDark = useDark() // 根据鼠标位置和主题模式改变样式 const cursorStyle = computed(() => ({ left: `${x.value}px`, top: `${y.value}px`, opacity: isDark.value ? 0.8 : 0.5 })) -
错误处理:处理Composables可能抛出的错误
javascriptconst { data, error } = useFetch('/api/data') if (error.value) { console.error('请求失败:', error.value) } -
性能优化:对于计算密集型的操作,考虑使用防抖或节流
javascriptconst debouncedSearch = useDebounceFn(searchAPI, 500)