第七节:带你全面理解vue3: 其他响应式进阶API
前言:
针对vue3官网中, 响应式:进阶API 中, 我们在上一章中给大家讲解了shallowRef, shallowReactive, shallowReadonly几个API的使用.
本章主要对剩下的API 进行讲解, 我们先看一下官网中进阶API 都有那些

对于剩下这些API, 你需要了解他们创建目的, 是为了解决之前的API存在的那些痛点问题, 这样你就能更好的了解使用他们的细节.工作中就可以有的放矢的选择不同的API.
1. triggerRef
我们首先来分析一下triggerRefAPI 的使用
1.1. triggerRef 针对的痛点问题
我们先看一个痛点问题:
对于ref响应式数据的变化, vue帮我们处理副作用. 比如,页面的更新, watchEffect侦听器回调函数的调用等.
但对于浅层响应数据, 比如shallowRef创建的数据, 其深层并不具有响应性, 也就是说vue并没有监测这些数据的变化, 当对深层数据进行修改时, 并不会触发副作用, 比如页面不会自动刷新.
triggerRefAPI 就是为了解决shallowRef浅层响应式数据深层修改问题.
当深层修改时, 会强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
1.2. triggerRef 类型
类型:
function triggerRef(ref: ShallowRef): void
triggerRefAPI 函数接收一个shallowRefAPI 创建的数据, 作用就是强制触发这个浅层ref数据的副作用.
1.3. triggerRef 使用示例
示例:
<template>
<div>
<h3>shallowReadonly</h3>
<div>{{ count }}</div>
<div>{{ count2 }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly, shallowRef, triggerRef, watchEffect } from 'vue'
export default defineComponent({
setup() {
const count = ref({ num: 0 })
const count2 = shallowRef({ num: 0 })
// 对于ref 数据, 是深层响应式,
// 因此当我们通过count.value.num++ 修改数据时,依然会触发watchEffect副作用函数
watchEffect(() => {
console.log('count.value.num', count.value.num)
})
// 因为shallowRef 数据不是深层响应式, 只有.value 整体修改才会触发响应式
// 因为当我们通过count2.value.num++ 修改数据时,不会出发watchEffect 副作用函数
// 同时视图也不会发生更改
watchEffect(() => {
console.log('count2.value.num', count2.value.num)
})
// 修改数据
const change = () => {
// count.value.num++
count2.value.num++
// 如果希望shallowRef 深层数据修改后,触发视图更新
// 那么就需要使用triggerRef 手动触发更新
triggerRef(count2) // 手动更新count2
}
return { count, count2, change }
}
})
</script>
通过示例的运行结果, 你也可以看出. shallowRef创建响应式数据, 在深层数据发生变化时, 不会触发页面更新 和watchEffect的处理函数. 因为深层不具有响应性.
当我们手动调用triggerRef函数, 并将shallowRef创建数据作为参数, 就是告诉vue , 我们需要强制执行shallowRef数据的副作用. 此时页面将会更新, watchEffect处理函数也会自动执行
1.4. triggerRef 使用小结
在理解triggerRefAPI 的使用后, 针对该API, 我做了以下小结
triggerRef常与shallowRef搭配使用triggerRef会强制更新以shallowRef数据作为依赖的副作用,ref数据会自动触发这些副作用
我们需要注意的是: vue3只提供了triggerRef这个方法,但没有提供triggerReactive的方法。 也就是说triggerRef 【不可以】去更改 shallowReactive创建的数据
2. toRaw
根据一个 Vue 创建的代理返回其原始对象
2.1. toRaw 针对的问题
在vue3中, 我们通过 reactive()、readonly()、shallowReactive() shallowReadonly()四个API 创建的响应式数据, 本质上就是通过Proxy创建的代理对象.
但有时我们在做数据传输时, 我们并不需要传响应式数据, 我们只想传最基本的原始对象.
toRawAPI 的作用就是返回 reactive()、readonly()、shallowReactive(),shallowReadonly() 创建的代理对应的原始对象。
2.2. toRaw 类型
toRaw 函数签名
function toRaw<T>(proxy: T): T
toRawAPI 函数接收一个Proxy代理对象(响应式对象)作为参数,
2.3. toRaw 使用示例
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ user }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw } from 'vue'
export default defineComponent({
setup() {
// 代理目标对象
const obj = { name: '张三', age: 18 }
// reactive 处理的代理对象
const user = reactive(obj)
// 控制触发代理对象
console.log('user', user)
// 使用toRaw, 参数是代理对象, 返回代理对象的目标对象
console.log('toRaw(user)', toRaw(user))
console.log('toRaw(user) === obj', toRaw(user) === obj) // true
// 修改数据
const change = () => {
user.name = '李四'
}
return { user, change }
}
})
</script>
通过控制台输出结果, 你可以看出, toRaw 就是获取代理对象的原目标对象.
这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
这句话来自于官网, 这句话你可以这么理解,
代理对象具有响应性, 可以理解为vue在监测这个数据的变化, 这个监测会消耗性能. 如果你的操作不要触发副作用, 就没有必要 使用具有响应性的代理对象.
比如调用接口时传入的参数, 就可以使用toRaw去掉代理对象的外壳, 获取到原始对象传入接口.
3. markRaw
markRaw 函数的作用就是将一个对象转为不可代理对象.
如果使用reactiveAPI , 也不会代理markRaw函数返回的对象, 会直接返回原对象.
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ user }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, markRaw, reactive } from 'vue'
export default defineComponent({
setup() {
// 代理目标对象
const obj = { name: '张三', age: 18 }
// 将obj原始对象标记为不可代理
const markObj = markRaw(obj)
// reactive 处理的代理对象
const user = reactive(markObj)
// user 不是代理对象
console.log('user', user)
// 修改数据
const change = () => {
user.name = '李四'
}
return { user, change }
}
})
</script>
控制台输出:

通过控制台输出结果, 可以看出, 通过markRaw 处理过的对象具有一个__v_skip的属性, 用于标记这个对象不能创建代理对象, 即响应式数据.
尽管你将该对象传入reactive, 返回的也不是一个代理对象, 而是原对象.
既然不是响应数据,修改user.name 时, 就不会触发视图更新
该API的作用就是, 帮助你给一些你不希望创建为代理对象的原始对象添加标记.
4. effectScope
4.1. effectScope 作用
在vue3的使用过程中,我们可能会针对同一个响应式数据创建多个副作用.比如computed, watch, watchEffect等.
再次过程中, 如果关闭某个副作用, 比如watch创建的侦听器, 就需要通过返回值关闭. 那么多个副作用你就需要一个一个关闭. 使用相对麻烦
effectScope字面意思就是副作用作用域, 可以理解为, 该函数创建一个作用域, 将所有的副作用放在共同一个作用域中, 如果以后想统一关闭副作用, 就可以使用作用域整体关闭.
4.2. effectScope
类型
function effectScope(detached?: boolean): EffectScope
interface EffectScope {
run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
stop(): void
}
effectScope函数返回一个作用域对象, 即EffectScope类型.
该作用域对象上具有run, stop方法, 同时run方法接收一个回调函数作为参数.
4.3. effectScope 使用方式
通过effectScope函数创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ count }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, effectScope, markRaw, reactive, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'
export default defineComponent({
setup() {
// 创建ref 数据
const count = ref(10)
// 创建副作用作用域
const scope = effectScope()
// 控制台输出 effect 作用域
console.log("scope", scope);
// 收集运行的副作用
scope.run(() => {
// 计算属性副作用
const computedCount = computed(() => count.value * 2
)
// watch 侦听副作用
watch(
count,
() => {
console.log('computedCount', computedCount.value)
console.log('watch count', count.value)
}
)
// watchEffect 副作用
watchEffect(() => {
console.log('watchEffect count', count.value)
})
})
console.log('scope', scope)
// 2秒以后关闭所有的副作用
setTimeout(() => {
scope.stop()
}, 2000)
// 修改数据
const change = () => {
count.value++
}
return { count, change }
}
})
</script>
控制台输出结果:

通过控制台输出的effect作用域对象, 你可以看到, 作用域将回调函数中的副作用进行了收集, 存储在effects属性上.
同时effect作用域对象原型对象上具有run收集副作用的方法, stop关闭副作用的方法.
5. getCurrentScope
getCurrentScope函数返回当前活跃的 effect 作用域。
在前一个API中, 给大家讲解了effectScope函数, 该函数执行后会返回一个effect 作用域, 通过调用effect作用域对象的run方法收集所有副作用. 我们就可以在run方法的回调函数中, 通过getCurrentScope函数获取到正在活跃的effect作用域对象.
示例:
// 创建副作用作用域
const scope = effectScope();
console.log("scope", scope);
// 收集运行的副作用
scope.run(() => {
// 计算属性副作用
const computedCount = computed(() => count.value * 2);
// watch 侦听副作用
watch(count, () => {
console.log("computedCount", computedCount.value);
console.log("watch count", count.value);
});
// watchEffect 副作用
watchEffect(() => {
console.log("watchEffect count", count.value);
});
// 通过 getCurrentScope() 获取当前真正活跃的 effect 作用域对象
const effectScope = getCurrentScope();
console.log("getCurrentScope", effectScope === scope);
// 控制台输出结果: getCurrentScope true
});
示例中, 我们通过effectScope创建了一个effect作用域对象, 当调用该作用域对象的run方法,传入回调函数, 会自动执行回调函数, 收集副作用, 并将收集到的副作用保存在副作用effect作用域中. 也就是说, 在执行回调函数时, 我们创建的scope就是活跃的effect作用域
之后,我们通过执行getCurrentScope函数获取当前活跃的副作用作用域, 和之前我们创建的作用域对比, 发现getCurrentScope 获取的就是我们创建的effect作用域.
其实每一个组件都有一个effect作用域, 用于收集组件内所有的副作用. 组件更新函数本身也就是一个副作用. 这也就是响应式数据变化后, 页面会重新渲染的原因.
以及组件被销毁后, vue3 会通过组件的effect作用域清理组件内收集的所有副作用
该API 在工作中并不常使用到. 甚至一个项目里连一次都不会用到.
6. onScopeDispose
该API 函数主要用于调试, 工作中也不怎么常用, 其作用就是在当前活跃的副作用(effect)作用域对象上注册一个调试的回调函数. 在effect作用域关闭时, 会自动调用注册的回调函数,.
示例:
// 创建副作用作用域
const scope = effectScope();
console.log("scope", scope);
// 收集运行的副作用
scope.run(() => {
// 计算属性副作用
const computedCount = computed(() => count.value * 2);
// watch 侦听副作用
watch(count, () => {
console.log("computedCount", computedCount.value);
console.log("watch count", count.value);
});
// watchEffect 副作用
watchEffect(() => {
console.log("watchEffect count", count.value);
});
// 在当前活跃的 effect 作用域对象上注册一个回调函数
onScopeDispose(() => {
console.log("当前effectScope 停止");
});
});
// 2秒以后关闭所有的副作用
setTimeout(() => {
scope.stop();
}, 2000);
示例中, 我们在effectScope收集副作用时, 通过onScopeDispose函数注册了一个回调函数.
在effectScope副作用作用域, 即scope对象调用stop方法时, 会自动执行注册的回调函数. 多用于功能调试
7. 结语
至此, 就把vue3中响应式进阶API 中剩余的API函数给大家讲完了, 这里比较常用的API 有triggerRef, toRaw, markRaw, effectScope, 其余两个API 函数并不怎么常用.
这里尤其要注意effectScope, 使用好了可以给代码增色不少.
转载自:https://juejin.cn/post/7372393680596205594