# Vue3教程 - 24 其他API
下面介绍一下 Vue3 中其他可能用到的 API,了解一下,万一需要呢。
# 24.1 shallowRef
shallowRef
是用于创建浅层响应式数据的API,和 ref
的区别是 shallowRef
只对第一层数据的变化进行响应式处理,这意味着,如果通过shallowRef
创建了一个包含复杂对象的引用,那么只有当该对象本身(即引用)发生变化时,才会触发响应式更新;而对象内部属性的变化则不会触发更新。
举个栗子:
<template>
<div>
<div>{{ count }}</div>
<div> {{ person.name }} - {{ person.age }}</div>
<button @click="changeData1">修改数据1</button>
<button @click="changeData2">修改数据2</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { shallowRef } from 'vue';
// 创建浅层响应式数据
let count = shallowRef(0);
let person = shallowRef({
name: 'doubi',
age: 13
});
function changeData1() {
count.value ++; // 触发响应
person.value = {name: 'niubi', age: 14}; // 触发响应
}
function changeData2() {
person.value.name = 'caibi'; // 不会触发响应
person.value.age = 15; // 不会触发响应
}
</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
29
30
31
32
在上面的代码中,通过 person.value.name
修改数据是无法触发响应的,只在修改第一层数据的时候触发响应。
# 24.2 shallowReactive
shallowReactive
和 shallowRef
是类似的。
举个栗子:
<template>
<div>
<div> {{ person.name }} </div>
<div> {{ person.birthday.year }} </div>
<button @click="changeData1">修改数据1</button>
<button @click="changeData2">修改数据2</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { shallowReactive } from 'vue';
// 创建浅层响应式数据
let person = shallowReactive({
name: 'doubi',
birthday: {
year: 2024
}
});
function changeData1() {
person.name = 'niubi'; // 触发响应
}
function changeData2() {
person.birthday.year = 2025 // 不会触发响应
}
</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
29
30
31
在上面的代码中也是一样的,修改对象的第一层数据可以触发响应,更深层次的不会触发响应。
当你需要创建一个响应式的引用,但又不希望Vue跟踪其内部属性的变化时,或者在处理大型对象或深层嵌套对象时,可以使用shallowRef
和 shallowReactive
减少不必要的性能开销。
# 24.3 readonly
readonly
用来创建只读的响应式数据,也就是在可读写的响应式数据上创建一个副本,这个副本是只读的,但是修改原数据的时候,这个副本的数据也会响应变化。
如果想要传递数据给别人,但是不希望别人修改,可以通过这样的形式,这样别人可以拿到数据,而且是响应式的,但是无法修改。
举个栗子:
<template>
<div>
<div>{{ count }}</div>
<div> {{ countReadonly }}</div>
<button @click="changeData">修改数据</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { readonly, ref } from 'vue';
let count = ref(0);
let countReadonly = readonly(count); // 创建只读数据
function changeData() {
count.value ++; // 触发响应
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面使用 let countReadonly = readonly(count);
创建了一个只读的响应式数据 countReadonly
, countReadonly
会跟随 count
数据进行响应。
上面使用的是 ref
创建的响应式数据,使用 reactive
创建对象响应式数据,然后使用 countReadonly
创建只读数据也是一样的。
# 24.4 shallowReadonly
readonly
是创建深层次的只读响应式数据,shallowReadonly
是创建浅层次的只读响应式数据。
也就是使用 readonly
创建的只读数据,哪一层都不能改,而使用 shallowReadonly
创建的只读数据,只有第一层不能改,其它层可以改。
举个例子:
<template>
<div>
<div> {{ person.name }} </div>
<div> {{ person.birthday.year }} </div>
<button @click="changeData">修改数据1</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive, shallowReadonly } from 'vue';
// 创建浅层响应式数据
let person = reactive({
name: 'doubi',
birthday: {
year: 2024
}
});
let personShallowReadonly = shallowReadonly(person); // 创建只读响应式数据
function changeData() {
// personShallowReadonly.name = 'niubi'; // 浅层次属性,无法修改
personShallowReadonly.birthday.year = 2025 // 深层次属性,可以修改
}
</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
上面使用了 shallowReadonly
创建了浅层次的只读数据,所以浅层次,一级的属性无法修改,深层次的属性可以修改。
# 24.5 toRaw
toRaw
的作用是获取响应式对象的原始对象。我们在之前创建响应式数据的时候,响应式数据是对普通对象的包装,这个 toRaw
就是用来获取普通对象的。
这个有什么使用场景呢?
有时候可能需要将原始对象数据提供给其他的外部使用,例如将数据提交给后端服务器,肯定不能将响应式对象提交给后端,还是要将其中的原始数据提交给后端。或者当你需要直接操作原始数据,但不希望触发响应更新页面,可以使用 toRaw
。
所以下面演示一下 toRaw 的使用:
<template>
<div>
<div> {{ person.name }} </div>
<div> {{ person.birthday.year }} </div>
<button @click="changeData">修改数据</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive, toRaw } from 'vue';
// 创建浅层响应式数据
let person = reactive({
name: 'doubi',
birthday: {
year: 2024
}
});
const rawPerson = toRaw(person);
function changeData() {
rawPerson.name = 'niubi'; // 修改原始数据不会导致页面渲染,但是修改的也是响应式的数据
console.log('person:', person);
console.log('rawPerson:', rawPerson);
}
</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
29
30
31
注意,虽然修改原始数据不会引起页面渲染,但是响应式数据中的数据也是同步更新的。谨慎使用。
# 24.6 markRaw
markRaw
是一个函数,用于标记对象,使其永远不会被转换为响应式对象。
什么意思呢?
举个栗子:
<template>
<div>
<div> {{ person.name }} </div>
<button @click="changeData">修改数据</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive, markRaw } from 'vue';
// 使用markRaw标记
let person1 = markRaw({
name: 'doubi',
age: 13
});
// 创建响应式数据,但是无效
let person2 = reactive(person1);
function changeData() {
person2.name = 'niubi'; // 不是响应式数据了
}
</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
上面使用 markRaw
标记了person1
对象,那么 person1 永远不会被转换为响应式对象。即使后面使用 person2 = reactive(person1);
创建响应式对象也是无效的。
使用 markRaw
是为了防止错误的将一些第三方的库变成响应式对象,避免不必要的性能开销。
# 24.7 customRef
customRef
也就是自定义 ref,有了 ref 为什么还需要自定义 ref 呢?
customRef
可以允许我们对追踪依赖和触发更新的行为进行细粒度的控制。
例如去抖动,去抖动是一种常见的性能优化技术,尤其是在处理用户输入时,避免每次输入变化都触发响应式更新,而是等到用户停止输入一段时间后再更新。
举个栗子:
customRef
接收的是一个函数,在函数中,需要添加 get
和 set
函数,用来获取和设置属性。
页面使用数据的时候会调用 get
函数,修改属性的时候,会调用 set
函数。
<template>
<div>
<input type="text" v-model="msg" />
<button @click="changeData">修改数据</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { customRef } from 'vue';
// 创建了防抖动的ref,使用customRef实现
function useDebouncedRef(value: string, delay = 1000) {
let timeout: number;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => { // 延迟指定时间才设置
value = newValue;
trigger();
}, delay);
}
};
});
}
const msg = useDebouncedRef('');
function changeData() {
msg.value = 'Hello'
}
</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
29
30
31
32
33
34
35
36
37
38
39
在上面的代码中,使用 customRef
封装了一个防抖动的功能。
- 在 get 函数中需要调用 track() 函数,告诉 Vue 响应式系统追踪这个属性值,当属性值发生变化就通过使用这个属性值的地方更新。
- 在 set 函数中需要调用 trigger() 函数,告诉 Vue 响应式系统属性值发生了变化,触发更新。
在实际的开发中可以将上面的 useDebouncedRef
使用 hooks 的方式单独在文件中创建,然后引入,使用起来就很方便优雅了。
customRef
提供了极大的灵活性,使你能够在 Vue 3 的响应式系统中实现自定义行为,满足各种特定的应用场景。
# 24.8 Teleport
我们将子组件放在父组件中,页面渲染后,子组件就是在父组件的 DOM 节点中的,但是使用 Teleport
可以将子组件渲染到 DOM 树中的任何位置。在处理模态框、通知栏、下拉菜单等时候,可能会用到。
举个栗子:
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<!-- 子窗口 -->
<div v-if="showModal" class="modal">
<p>这是一个模态框</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue';
let showModal = ref(false);
</script>
<style scoped>
.modal {
border: 1px solid #333333;
border-radius: 10px;
padding: 20px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</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
在上面的代码中,创建了一个子窗口,并使用 showModal
控制显示,这个子窗口在页面显示的时候肯定是显示在父组件中的。
如下图:
如果想让元素渲染在指定的位置,那么可以通过 <teleport>
标签和其 to
属性实现内容的“传送”功能。to
属性接受一个 CSS 选择器字符串或真实的 DOM 节点,表示要将内容渲染到的目标位置。
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<p>这是一个模态框</p>
<button @click="showModal = false">关闭</button>
</div>
</teleport>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue';
let showModal = ref(false);
</script>
<style scoped>
.modal {
border: 1px solid #333333;
border-radius: 10px;
padding: 20px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</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
上面使用 <teleport to="body">
将其中的内容渲染在 body
标签下,如果想渲染在指定的 id
元素下,还可以使用 <teleport to="#元素ID">
,使用的是 CSS 选择器。
显示如下:
在某些情况下,可能需要根据运行时的条件动态决定 Teleport 的目标位置。这可以通过在 to
属性中绑定一个动态的值来实现:
<teleport :to="target">
</teleport>
<script lang="ts" setup>
import { ref } from 'vue';
let target = ref('body');
</script>
2
3
4
5
6
7
8