vueuse实践及分析
# 简介
VueUse
是一个基于 Composition API
的实用函数集合。通俗的来说,这就是一个工具函数包,它可以帮助你快速实现一些常见的功能,免得你自己去写,解决重复的工作内容。以及进行了基于 Composition API
的封装。让你在 vue3
中更加得心应手。
VueUse 通过vue-demi (opens new window)的强大功能在单个包中适用于 Vue 2 和 3!;ueUse是基于Compomit API (opens new window)的实用程序函数的集合。
如果想看到每一个实用程序的完整列表,我绝对建议你去看看官方文档 (opens new window)。但总结一下,VueUse中有9种类型的函数。
- Animation——包含易于使用的过渡、超时和计时功能。
- Browser——可用于不同的屏幕控制、剪贴板、偏好等。
- Component——提供了不同组件方法的简写。
- Formatters——提供响应时间格式化功能。
- Sensors——用来监听不同的DOM事件、输入事件和网络事件。
- State——管理用户状态(全局、本地存储、会话存储)。
- Utility——不同的实用函数,如 getter、条件、引用同步等。
- Watch——更多高级类型的观察器,如可暂停的观察器、退避的观察器和条件观察器。
- Misc——不同类型的事件、WebSockets和web workers 的功能
这些类别中的大多数都包含几个不同的功能,所以VueUse对于你的使用情况来说是很灵活的,可以作为一个很好的地方来快速开始构建Vue应用程序。
总体上分为以下几个类别提供工具函数:
- 动画
- 浏览器
- 组件
- 格式化
- 传感器
- State(状态机)
- 公共方法
- 监听
- 杂项
# 安装
# npm
npm i @vueuse/core
npm i @vueuse/core @vueuse/components
2
3
# cdn
<script src="https://unpkg.com/@vueuse/shared"></script>
<script src="https://unpkg.com/@vueuse/core"></script>
2
建议使用NPM,因为它使用法更容易理解,但如果我们使用CDN,VueUse将在应用程序中通过 window.VueUse
访问。
对于NPM的安装,所有的功能都可以通过使用标准的对象重构从 @vueuse/core
中导入,像这样访问。
import { useRefHistory } from '@vueuse/core'
好了,现在我们已经安装了VueUse,让我们在应用程序中使用它!
# 用法示例
# 基础示范
只需从中导入所需的功能@vueuse/core
import { useMouse, usePreferredDark, useLocalStorage } from '@vueuse/core'
export default {
setup() {
// 跟踪鼠标位置
const { x, y } = useMouse()
// 用户喜欢黑色主题吗
const isDark = usePreferredDark()
// 在localStorage中持久化状态
const store = useLocalStorage(
'my-storage',
{
name: 'Apple',
color: 'red',
},
)
return { x, y, isDark, store }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面从 VueUse
当中导入了三个函数, useMouse
, usePreferredDark
, useLocalStorage
。
useMouse
是一个监听当前鼠标坐标的一个方法,他会实时的获取鼠标的当前的位置。usePreferredDark
是一个判断用户是否喜欢深色的方法,他会实时的判断用户是否喜欢深色的主题。useLocalStorage
是一个用来持久化数据的方法,他会把数据持久化到本地存储中。
# 防抖 和 节流
import { useThrottle , useDebounce } from '@vueuse/core'
const input = ref('')
const throttled = useThrottle(input, 1000, false) // 延迟1s获取 input 的值
const debounced = useDebounce(input, 1000)
input.value = 'bar'
console.log(debounced.value) // 延迟1s 更新input的值
2
3
4
5
6
7
8
# component
中使用的函数
<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
const el = ref()
function close () {
/* ... */
}
onClickOutside(el, close)
</script>
<template>
<div ref="el">
Click Outside of Me
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面例子中,使用了 onClickOutside
函数,这个函数会在点击元素外部时触发一个回调函数。也就是这里的 close
函数。在 component
中就是这么使用
<script setup>
import { OnClickOutside } from '@vueuse/components'
function close () {
/* ... */
}
</script>
<template>
<OnClickOutside @trigger="close">
<div>
Click Outside of Me
</div>
</OnClickOutside>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意⚠️ 这里的
OnClickOutside
函数是一个组件,不是一个函数。需要package.json
中安装了@vueuse/components
。
# 全局状态共享的函数
// store.js
import { createGlobalState, useStorage } from '@vueuse/core'
export const useGlobalState = createGlobalState(
() => useStorage('vue-use-local-storage'),
)
// component.js
import { useGlobalState } from './store'
export default defineComponent({
setup() {
const state = useGlobalState()
return { state }
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
这样子就是一个简单的状态共享了。扩展一下。传一个参数,就能改变 store
的值了。
# fetch
简单的请求
import { useFetch } from '@vueuse/core'
const { isFetching, error, data } = useFetch(url)
2
它还有很多的 option
参数,可以自定义。
// 100ms超时
const { data } = useFetch(url, { timeout: 100 })
// 请求拦截
const { data } = useFetch(url, {
async beforeFetch({ url, options, cancel }) {
const myToken = await getMyToken()
if (!myToken) cancel()
options.headers = {
...options.headers,
Authorization: `Bearer ${myToken}`,
}
return {
options
}
}
})
// 响应拦截
const { data } = useFetch(url, {
afterFetch(ctx) {
if (ctx.data.title === 'HxH')
ctx.data.title = 'Hunter x Hunter' // Modifies the resposne data
return ctx
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 配置
这些显示了 VueUse 中大多数函数的常规配置。
# 事件过滤器
从 v4.0 开始,我们提供了事件过滤器系统,以便灵活地控制何时触发事件。例如,您可以使用 并控制事件触发速率:throttleFilter``debounceFilter
import { throttleFilter, debounceFilter, useLocalStorage, useMouse } from '@vueuse/core'
// 更改将写入localStorage,并设置1s
const storage = useLocalStorage('my-key', { foo: 'bar' }, { eventFilter: throttleFilter(1000) })
// 鼠标位置将在鼠标空闲100ms后更新
const { x, y } = useMouse({ eventFilter: debounceFilter(100) })
2
3
4
5
6
7
此外,您可以利用暂时暂停某些事件。pausableFilter
import { pausableFilter, useDeviceMotion } from '@vueuse/core'
const motionControl = pausableFilter()
const motion = useDeviceMotion({ eventFilter: motionControl.eventFilter })
motionControl.pause() // 运动更新停顿了一下
motionControl.resume()// 运动更新恢复
2
3
4
5
6
7
# 无功时序
VueUse 的功能遵循 Vue 的反应性系统默认值,尽可能进行冲洗计时 (opens new window)。
对于类似 -like 的可组合对象(例如,每当
(opens new window)使用时
(opens new window)暂停监视
(opens new window)存储使用 RefHistory,
(opens new window)默认值为 。这意味着它们将缓冲无效的效果并异步刷新它们。这可以避免在同一"tick"中发生多个状态突变时不必要的重复调用。watch``{ flush: 'pre' }
与 使用 的方式相同,VueUse 允许您通过传递以下选项来配置计时:watch``flush
const { pause, resume } = pausableWatch(
() => {
// 安全访问更新后的DOM
},
{ flush: 'post' }
)
2
3
4
5
6
刷新选项(默认:"pre")
'pre'
:在同一个"勾号"中缓冲无效效果,并在渲染之前刷新它们'post'
:像"pre"一样的异步,但在组件更新后触发,以便您可以访问更新的DOM'sync'
:强制效果始终同步触发
**注意:**对于类似可组合物(例如syncRef
(opens new window)controlledComputed
(opens new window),当刷新计时可配置时,默认值更改为将它们与 Vue 中计算的引用的工作方式保持一致。computed``{ flush: 'sync' }
# 可配置的全局依赖关系
在 v4.0 中,访问浏览器 API 的函数将提供一个选项字段,供您指定全局依赖项(例如 、和 )。默认情况下,它将使用全局实例,因此在大多数情况下,您无需担心它。在使用 iframe 和测试环境时,此配置非常有用。window``document``navigator
// 访问父上下文中
const parentMousePos = useMouse({ window: window.parent })
const iframe = document.querySelect('#my-iframe')
// 接触子上下文
const childMousePos = useMouse({ window: iframe.contextWindow })
// 测试
const mockWindow = /* ... */
const { x, y } = useMouse({ window: mockWindow })
2
3
4
5
6
7
8
9
# 组件
在 v5.0 中,我们引入了一个新包,提供可组合函数的无渲染组件样式用法。@vueuse/components
# 安装
$ npm i @vueuse/core @vueuse/components
# 使用
有关组件样式的详细用法,请参阅每个函数的文档。
例如onClickOutside
(opens new window) 以前
<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
const el = ref()
function close () {
/* ... */
}
onClickOutside(el, close)
</script>
<template>
<div ref="el">
Click Outside of Me
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
现在,您可以以组件方式使用它:
<script setup>
import { OnClickOutside } from '@vueuse/components'
function close () {
/* ... */
}
</script>
<template>
<OnClickOutside @trigger="close">
<div>
Click Outside of Me
</div>
</OnClickOutside>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
同样,您也可以使用 以下命令访问返回值:v-slot
<UseMouse v-slot="{ x, y }">
x: {{ x }}
y: {{ y }}
</UseMouse>
<UseDark v-slot="{ isDark, toggleDark }">
<button @click="toggleDark()">
Is Dark: {{ isDark }}
</button>
</UseDark>
2
3
4
5
6
7
8
9
useTemplateRefsList
useTemplateRefsList: 这个方法可以在 vue3 组件式 api
中帮助你快速绑定 for
循环中的 组件ref。比自己实现考虑得更加完备。
<script setup lang="ts">
import { onUpdated } from 'vue'
import { useTemplateRefsList } from '@vueuse/core'
const refs = useTemplateRefsList<HTMLDivElement>() // 用来存储元素 ref 的数组
onUpdated(() => {
console.log(refs)
})
</script>
<template>
<!-- 在这里绑定 ref -->
<div v-for="i of 5" :key="i" :ref="refs.set"></div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5个常用的函数
# useRefHistory
# useRefHistory 跟踪响应式数据的更改
useRefHistory
跟踪对Ref所做的每一个改变,并将其存储在一个数组中。这使我们能够轻松地为我们的应用程序提供撤销和重做功能。
让我们看一个示例,其中我们正在构建一个我们希望能够撤消的文本区域。
第一步是在不使用 VueUse 的情况下创建我们的基本组件——使用 ref、textarea 和用于撤消和重做的按钮。
<template>
<p>
<button> Undo </button>
<button> Redo </button>
</p>
<textarea v-model="text"/>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<style scoped>
button {
border: none;
outline: none;
margin-right: 10px;
background-color: #2ecc71;
color: white;
padding: 5px 10px;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
然后,让我们通过导入 useRefHistory
函数,然后从我们的文本 ref 中提取history、undo 和 redo 属性来添加 VueUse。这就像调用 useRefHistory
并传递我们的 ref 一样简单。
import { ref } from 'vue'
import { useRefHistory } from '@vueuse/core'
const text = ref('')
const { history, undo, redo } = useRefHistory(text)
2
3
4
5
每次我们的 ref 更改时,这都会触发一个观察者——更新我们刚刚创建的 history
属性。
然后,为了让我们能真正看到发生了什么,让我们打印出模板内的历史记录,同时在点击相应的按钮时调用我们的 undo
和 redo
函数。
<template>
<p>
<button @click="undo"> Undo </button>
<button @click="redo"> Redo </button>
</p>
<textarea v-model="text"/>
<ul>
<li v-for="entry in history" :key="entry.timestamp">
{{ entry }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
import { useRefHistory } from '@vueuse/core'
const text = ref('')
const { history, undo, redo } = useRefHistory(text)
</script>
<style scoped>
button {
border: none;
outline: none;
margin-right: 10px;
background-color: #2ecc71;
color: white;
padding: 5px 10px;;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
好的,让我们运行它。当我们输入时,每个字符都会触发历史数组中的一个新条目,如果我们点击undo/redo,我们会转到相应的条目。
还有不同的选项可以为此功能添加更多功能。例如,我们可以深入跟踪反应对象并限制这样的历史条目的数量。
const { history, undo, redo } = useRefHistory(text, {
deep: true,
capacity: 10,
})
2
3
4
有关完整的选项清单,请务必查看文档。
# onClickOutside
# onClickOutside 关闭模态
onClickOutside
检测在一个元素之外的任何点击。根据我的经验,这个功能最常见的使用情况是关闭任何模式或弹出窗口。
通常情况下,我们希望我们的模态挡住网页的其他部分,以吸引用户的注意力并限制错误。然而,如果他们真的点击了模态之外的内容,我们希望它能够关闭。
只需两个步骤即可完成此操作:
- 为我们要检测的元素创建一个模板引用
- 使用此模板引用运行 onClickOutside
这是一个使用 onClickOutside
的带有弹出窗口的简单组件。
<template>
<button @click="open = true"> Open Popup </button>
<div class="popup" v-if='open'>
<div class="popup-content" ref="popup">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Corporis aliquid autem reiciendis eius accusamus sequi, ipsam corrupti vel laboriosam necessitatibus sit natus vero sint ullam! Omnis commodi eos accusantium illum?
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
const open = ref(false) // state of our popup
const popup = ref() // template ref
// whenever our popup exists, and we click anything BUT it
onClickOutside(popup, () => {
open.value = false
})
</script>
<style scoped>
button {
border: none;
outline: none;
margin-right: 10px;
background-color: #2ecc71;
color: white;
padding: 5px 10px;;
}
.popup {
position: fixed;
top: ;
left: ;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: rgba(, , , 0.1);
}
.popup-content {
min-width: 300px;
padding: 20px;
width: 30%;
background: #fff;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
结果是这样的,我们可以用我们的按钮打开弹出窗口,然后在弹出内容窗口外点击关闭它。
# useVModel
# useVModel 简化了 v-model 绑定
Vue 开发人员的一个常见用例是为组件创建自定义 v-model 绑定。这意味着我们的组件接受一个值作为 prop,并且每当该值被修改时,我们的组件都会向父级发出更新事件。
useVModel函数将其简化为只使用标准的 ref
语法。假设我们有一个自定义的文本输入,试图为其文本输入的值创建一个 v-model
。 通常情况下,我们必须接受一个值的prop,然后emit一个变化事件来更新父组件中的数据值。
我们可以使用useVModel,把它当作一个普通的ref,而不是使用ref并调用 props.value
和 update:value
。这有助于减少我们需要记住的不同语法的数量!
<template>
<div>
<input
type="text"
:value="data"
@input="update"
/>
</div>
</template>
<script>
import { useVModel } from '@vueuse/core'
export default {
props: ['data'],
setup(props, { emit }) {
const data = useVModel(props, 'data', emit)
console.log(data.value) // equal to props.data
data.value = 'name' // equal to emit('update:data', 'name')
const update = (event) => {
data.value = event.target.value
}
return {
data,
update
}
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
每当我们需要访问我们的值时,我们只需调用 .value
,useVModel将从我们的组件props中给我们提供值。而每当我们改变对象的值时,useVModel会向父组件发出一个更新事件。
下面是一个快速的例子,说明该父级组件可能是什么样子...
<template>
<div>
<p> {{ data }} </p>
<custom-input
:data="data"
@update:data="data = $event"
/>
</div>
</template>
<script>
import CustomInput from './components/CustomInput.vue'
import { ref } from 'vue'
export default {
components: {
CustomInput,
},
setup () {
const data = ref('hello')
return {
data
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
结果看起来像这样,我们在父级中的值始终与子级中的输入保持同步。
# IntersectionObserver
# 使用IntersectionObserver 跟踪元素可见性
在确定两个元素是否重叠时,Intersection Observers (opens new window) 非常强大。一个很好的用例是检查元素当前是否在视口中可见。
本质上,它检查目标元素与根元素/文档相交的百分比。如果该百分比超过某个阈值,它会调用一个回调来确定目标元素是否可见。
useIntersectionObserver
提供了一个简单的语法来使用IntersectionObserver API。我们所需要做的就是为我们想要检查的元素提供一个模板ref。默认情况下,IntersectionObserver将以文档的视口为根基,阈值为0.1——所以当这个阈值在任何一个方向被越过时,我们的交集观察器将被触发。
这个例子的代码可能是这样的:我们有一个假的段落,只是在我们的视口中占据了空间,我们的目标元素,然后是一个打印语句,打印我们元素的可见性。
<template>
<p> Is target visible? {{ targetIsVisible }} </p>
<div class="container">
<div class="target" ref="target">
<h1>Hello world</h1>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
setup() {
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
},
)
return {
target,
targetIsVisible,
}
},
}
</script>
<style scoped>
.container {
width: 80%;
margin: auto;
background-color: #fafafa;
max-height: 300px;
overflow: scroll;
}
.target {
margin-top: 500px;
background-color: #1abc9c;
color: white;
padding: 20px;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
当我们运行并滚动它时,我们会看到它正确地更新了。
我们还可以为 Intersection Observer 指定更多选项,例如更改其根元素、边距(用于计算交点的根边界框的偏移量)和阈值级别。
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
},
{
// root, rootMargin, threshold, window
// full options in the source: https://github.com/vueuse/vueuse/blob/main/packages/core/useIntersectionObserver/index.ts
threshold: 0.5,
}
)
2
3
4
5
6
7
8
9
10
11
同样重要的是,这个方法返回一个 stop
函数,我们可以调用这个函数来停止观察交叉点。如果我们只想追踪一个元素在屏幕上第一次可见的时候,这就特别有用。
在这段代码中,一旦 targetIsVisible
被设置为 true
,观察者就会停止,即使我们滚动离开目标元素,我们的值也会保持为true
。
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
if (isIntersecting) {
stop()
}
},
)
2
3
4
5
6
7
8
9
# useTransition
# useTransition 在值之间过渡
useTransition
是整个veuse库中我最喜欢的函数之一。它允许我们在一行内平滑地转换数值。
我们有一个存储为ref的数字源和一个将在不同数值之间缓和的输出。例如,假设我们想建立一个计数器
我们可以通过三个步骤来做到这一点:
- 创建我们的
count
ref并将其初始化为零; - 使用
useTransition
创建output
ref(设置持续时间和转换类型); - 更改
count
的值;
<script setup>
import { ref } from 'vue'
import { useTransition, TransitionPresets } from '@vueuse/core'
const source = ref(0)
const output = useTransition(source, {
duration: 3000,
transition: TransitionPresets.easeOutExpo,
})
source.value = 5000
</script>
2
3
4
5
6
7
8
9
10
11
然后,在我们的模板中,我们希望显示 output
的值,因为它可以在不同值之间平滑过渡。
<template>
<h2>
<p> Join over </p>
<p> {{ Math.round(output) }}+ </p>
<p>Developers </p>
</h2>
</template>
<script setup>
import { ref } from 'vue'
import { useTransition, TransitionPresets } from '@vueuse/core'
const source = ref()
const output = useTransition(source, {
duration: 3000,
transition: TransitionPresets.easeOutExpo,
})
source.value = 5000
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这就是结果!
我们还可以使用 useTransition
来过渡整个数字数组,这在处理位置或颜色时很有用。 处理颜色的一个绝招是使用一个计算属性将RGB值格式化为正确的颜色语法。
<template>
<h2 :style="{ color: color } "> COLOR CHANGING </h2>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useTransition, TransitionPresets } from '@vueuse/core'
const source = ref([, , ])
const output = useTransition(source, {
duration: 3000,
transition: TransitionPresets.easeOutExpo,
})
const color = computed(() => {
const [r, g, b] = output.value
return `rgb(${r}, ${g}, ${b})`
})
source.value = [255, , 255]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
一些进一步定制的酷方法是使用任何内置的过渡预设或使用CSS缓动函数来定义我们自己的过渡。
# 最佳实践
# 解构
VueUse 中的大多数函数都返回一个refs 对象,您可以使用ES6 的对象析构语法来获取所需的内容。例如:
import { useMouse } from '@vueuse/core'
// “x”和“y”是引用
const { x, y } = useMouse()
console.log(x.value)
const mouse = useMouse()
console.log(mouse.x.value)
2
3
4
5
6
7
8
如果您希望将它们用作对象属性样式,则可以使用 打开 ref 的包装。例如:reactive()
import { reactive } from 'vue'
import { useMouse } from '@vueuse/core'
const mouse = reactive(useMouse())
// "x"和"y"将自动展开, 不需要 `.value`
console.log(mouse.x)
2
3
4
5
6
7
# 常规
- 从以下位置导入所有 Vue API
"vue-demi"
- 尽可能使用
ref
代替reactive
- 尽可能使用选项对象作为参数,以便为将来的扩展提供更灵活的操作。
- 当包装大量数据时,使用
shallowRef
代替ref
。 - 在使用全局变量时使用
configurableWindow
(等等),比如window
可以灵活地处理多个窗口、测试模拟和SSR。 - 当涉及尚未由浏览器广泛实现的 Web API 时,还会输出标志
isSupported
- 在内部使用
watch
或watchEffect
时,也要尽可能配置immediate
和flush
选项 - 使用
tryOnUnmounted
(opens new window)来优雅地清除副作用 - 避免使用控制台日志
# 参考链接
https://vueuse.org/