前言:
针对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): EffectScopeinterface EffectScope {run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefinedstop(): 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, 使用好了可以给代码增色不少.