前言
VueUse 是什么?根据官网的说法,VueUse 是依据 Vue 组合 API 的工具函数的集合。
那么为什么要使用 VueUse? 当然是为了使得代码编写更加高效和快捷。
接下来我们以 useCounter(计数器) 为例子,探索下 VueUse 的用法和实现原理。
基础用法
从官方文档可以看到,基础用法如下:
<script setup>
import { useCounter } from '@vueuse/core'
const { count, inc, dec, set, reset } = useCounter(1, { min: 0, max: 16 })
</script>
<div>
{{count}}
<div @click="inc()">+</div>
<div @click="dec()">-</div>
<div @click="inc(3)">+3</div>
<div @click="reset()">reset</div>
</div>
引入@vueuse/core后,我们就可以通过useCounter()的方式引入变量 count 以及 inc、dec、reset 等函数。
很简单的用法,点击 + 时数字+1,点击 - 时数字 -1,也可以传参想要加减的幅度。点击 reset 时会恢复到传参 1。
可以去 playground 在线测试下。
实现原理
完整源码
// packages\shared\useCounter\index.ts
import type { MaybeRef, Ref } from 'vue'
import {
shallowReadonly,
shallowRef,
// eslint-disable-next-line no-restricted-imports
unref,
} from 'vue'
export interface UseCounterOptions {
min?: number
max?: number
}
export interface UseCounterReturn {
/**
* The current value of the counter.
*/
readonly count: Readonly<Ref<number>>
/**
* Increment the counter.
*
* @param {number} [delta=1] The number to increment.
*/
inc: (delta?: number) => void
/**
* Decrement the counter.
*
* @param {number} [delta=1] The number to decrement.
*/
dec: (delta?: number) => void
/**
* Get the current value of the counter.
*/
get: () => number
/**
* Set the counter to a new value.
*
* @param val The new value of the counter.
*/
set: (val: number) => void
/**
* Reset the counter to an initial value.
*/
reset: (val?: number) => number
}
/**
* Basic counter with utility functions.
*
* @see https://vueuse.org/useCounter
* @param [initialValue]
* @param options
*/
export function useCounter(initialValue: MaybeRef<number> = 0, options: UseCounterOptions = {}) {
let _initialValue = unref(initialValue)
const count = shallowRef(initialValue)
const {
max = Number.POSITIVE_INFINITY,
min = Number.NEGATIVE_INFINITY,
} = options
const inc = (delta = 1) => count.value = Math.max(Math.min(max, count.value + delta), min)
const dec = (delta = 1) => count.value = Math.min(Math.max(min, count.value - delta), max)
const get = () => count.value
const set = (val: number) => (count.value = Math.max(min, Math.min(max, val)))
const reset = (val = _initialValue) => {
_initialValue = val
return set(val)
}
return { count: shallowReadonly(count), inc, dec, get, set, reset }
}
首先,我们可以看到,useCounter函数的参数是 initialValue 和 options,在例子中我们将初始值设置为 1,配置项 options 设置了{ min: 0, max: 16 }。
在函数内部,初始值会记录在变量 _initialValue 以及 count 中,
当每次触发 inc 函数时,默认增幅 delta 是 1(可以重新传参),count的值会更新为Math.max(Math.min(max, count.value + delta), min)。也就是说,在原 count 的基础上加上增幅 delta,并控制不超过 max,实现方法是Math.min(max, count.value + delta)。还要控制不限于 min,实现方法是Math.max(xxx, min)。
类似地,dec 函数是差不多的实现方法。
get 和 set 函数是获取数值以及设置新的数值。
reset 函数,可以重置为传入的值或者之前保存的初始值 _initialValue,调用 set 方法去设置。
其次,我们不难发现,代码中作者从 vue 中引入了 MaybeRef, shallowReadonly, shallowRef, unref 等,这些是什么意思呢?
MaybeRef, unref
在 VueUse 中,MaybeRef 是一个类型工具,表示一个值可以是普通值或 Vue 的响应式引用(Ref)。这种设计使得 API 更加灵活,既支持直接传入静态值,也支持响应式变量。
export type MaybeRef<T = any> = T | Ref<T>;
在代码中,初始值 initialValue 是 MaybeRef 类型的,说明我们可以传参:
- 基础变量(如 42)
- Vue 的 Ref 对象(如 ref(42))
而内部变量 _initialValue ,会经过 unref() 处理:
let _initialValue = unref(initialValue)
换句话说, unref 会将 ref 变量或者基础变量转为基础变量返回:
export declare function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T;
使用场景:
const a = ref(1)
const b = 2
console.log(unref(a)) // 1
console.log(unref(b)) // 2
shallowReadonly
shallowReadonly 用于创建一个浅层只读的响应式对象。特点是:
- 只读性:返回的对象是只读的,不能直接修改其属性
- 浅层响应:只会对对象的第一层属性做响应式处理,嵌套对象保持原样
因此,shallowReadonly 性能比 readonly 更好,适合不需要深度响应式的场景。
源码是:
export declare function shallowReadonly<T extends object>(target: T): Readonly<T>;
在 useCounter 中,返回 count 时用到了:
return { count: shallowReadonly(count), inc, dec, get, set, reset }
这样做的目的是:
防止外部直接修改 count.value(强制通过提供的 inc/dec/set 方法修改)
保持性能优化,因为计数器值本身就是基本类型数字,不需要深度响应式
shallowRef
shallowRef 用于创建一个浅层响应式的引用(Ref)。它与常规 ref 的主要区别是:
- 浅层响应:只对 .value 本身做响应式处理,不会递归转换嵌套对象
- 性能优化:比 ref 更轻量,适合性能敏感、不需要深度响应式的场景
在 useCounter 中的使用:
const count = shallowRef(initialValue) // 创建一个浅层响应式计数器
当 count.value 被修改时,会触发更新;如果传入的 initialValue 是对象,那么修改对象内部属性时不会触发更新(除非替换整个对象)。
对比 ref:
const deepRef = ref({ a: 1 })
const shallow = shallowRef({ a: 1 })
deepRef.value.a = 2 // 会触发依赖更新
shallow.value.a = 2 // 不会触发更新
后记
麻雀虽小,五脏俱全。本文以最简单的 useCounter(计数器) 为例子,探索 VueUse 的用法和实现原理。
使用 VueUse,可以让我们编写代码更加地高效和快捷。