Vue3
不仅为我们提供了ref
和reactive
这样的响应式函数,还进一步丰富了它们的辅助函数族群,如shallowRef()
和shallowReactive()
,使得我们在处理复杂的响应式数据时有了更多的选择和灵活性。它们为我们在适度控制数据的响应性以达到优化性能的目标提供了可能
Vue 3引入了新的API使得响应性更细粒度,主要包括两个函数:shallowRef()
和shallowReactive()
。
此函数创建一个Ref
对象,它包装了传递进来的原始值。不过与ref()
不同,shallowRef()
并不会将嵌套的对象转换为响应式数据。这主要用于我们需要引用一些比较庞大的对象或数组,而又不希望它们变为响应式数据时。
例如:
import { shallowRef, triggerRef } from 'vue'
const count = shallowRef(0)
console.log(count.value)
// 0
triggerRef(count)
// 强制轮询count的更新
注意点:如果在模板中使用shallowRef()
,则无论其内部数据是否发生变化,都会被自动解包。如果我们需要嵌套使用或者不希望被解包,我们应当使用.value
属性。
此函数用于创建一个浅层响应式对象,即只对顶层属性做代理,而不进行深层次的数据绑定。这在你有大量静态数据需要被代理,但又不希望产生太多的性能开销时非常有用。
例如:
import { shallowReactive, reactive } from 'vue'
const shallow = shallowReactive({ foo: { bar: 1 }})
const deep = reactive({ foo: { bar: 1 }})
console.log(shallow.foo === deep.foo)
// false
注意点:Vue 3推崇的编程风格是"composition",即组合函数来产生新的行为。如果你取一个组合对象的子属性并监听它,那么你有责任确保你不会遗漏任何对这个子属性的更新。浅层响应对象不会自动深层监听子对象,所以如果你有这种需求,你应该使用reactive()
。
Vue 3引入了一个新的全局API,readonly
,用于创建一个只读版本的响应式数据。类似于reactive
API,readonly
也返回一个代理,但这个代理不允许修改其包装的对象:
const original = reactive({ count: 0 })
// 创建一个只读的代理
const copy = readonly(original)
console.log(copy.count) // 0
copy.count++ // 尝试更改copy,将会抛出错误
对于开发人员进行试图修改只读代理的任何尝试,JavaScript将抛出错误(在严格模式或支持Proxy.handler.set
抛出错误的 JavaScript 平台上)或者简单地忽略它。
注意:
readonly
对象在传递给readonly
或者reactive
时将被解包,就像reactive
一样。除此之外 Vue3 还提供了一个 shallowReadonly
全局 API 用于创建一个将其对象视为只读数据但不对其内部对象进行深度响应式转换的版本:
const obj = shallowReadonly({
count: 0,
nested: {
anotherCount: 1
}
})
console.log(obj.count) // 0
obj.count++ // 尝试更改copy出错
obj.nested.anotherCount++ // 不会报错
在此例子中,尝试改变 obj.count
的值会导致抛出错误,但 nested.anotherCount
可以被成功更改,因为 obj.nested
没有被转换为只读的代理。
总的来说,使用 readonly
和 shallowReadonly
可以在我们希望某些数据保持不变,同时给予 Vue 追踪它们的能力时,提供很有用的功能。举例来说,我们可以使用这些 API 来确保应用的某些部分(如来自服务器的数据),不会被意外地修改。然而,也有一些限制和注意事项,比如在某些运行时环境中,尝试修改只读数据可能会被忽略而不是抛出错误,开发者需要对此有所了解。
toRaw
和markRaw
是 Vue 3 提供的新的 API,主要用于处理对象或值的响应式系统中的场景。
Vue3 使用了 Proxy 进行数据劫持,响应式数据原本的值会被 Proxy 包裹。toRaw
可以获得这个包裹之后的原始值,如果传入的值不是 Proxy,返回的就是传入的值。
let obj = reactive({ x: 1 })
console.log(obj) // Proxy {…}
let raw = toRaw(obj)
console.log(raw) // {x: 1}
使用toRaw
的情况,可能是你希望获得对象的原始值,而不影响其响应性。例如,当你想要避免触发依赖项追踪,或者在不直接修改原始对象的情况下,运行原始对象上的方法。
markRaw
用于标记一个对象,使得其永远不会转化为 Proxy。
let obj = { x: 1 }
let markObj = markRaw(obj)
let reactiveObj = reactive(markObj)
console.log(reactiveObj === obj) // true
它适用于希望 Vue 跳过代理的对象,或者是 Vue 初始化之前就已经存在,并且不希望被 Vue 响应化的对象。
需要注意的是,被markRaw
标记的对象,将不再具有响应性,一般在高性能的计算或者特定的库中使用。
其中,使用markRaw
和toRaw
需要注意以下几点:
toRaw
和markRaw
不应随便使用,过度使用会破坏 Vue 的 Reactivity 系统。markRaw
标记的对象,Vue 不会去做依赖收集和更新操作。换句话说 Vue 对于这个对象就是"视而不见"。toRaw
处理的对象,仍然是响应式的。只是你在某一次希望获取它的原始值,避免 Vue 的依赖收集或对这个对象进行更深层次的 Proxy。总的来说,Vue 提供toRaw
和markRaw
这两个 API 的目的,不是让你在项目中到处使用,而是给有特殊需求的场景提供可能。在大部分使用 Vue 的场景下,你都不需要使用它们。
Vue3中的customRef
是一个用于创建自定义响应式引用的函数。
它的基本用法是接受两个函数,一个用于获取引用的值,一个用于设置引用的值。这两个函数分别在存储的值被接触或更改时运行。
注意点:
customRef
返回的是一个响应式引用,即便源数据非响应式,也会变为响应式。
customRef
的两个函数get
和set
都会在特定的时间运行,不要在这两个函数中进行一些复杂或者副作用的操作。
举例:
import { customRef } from 'vue'
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => ({
get() {
track() // 触发依赖追踪
return value
},
set(newValue) {
clearTimeout(timeout) // 清除上一次的定时器
timeout = setTimeout(() => {
value = newValue
trigger() // 触发响应
}, delay)
}
}))
}
let count = useDebouncedRef(0)
console.log(count.value) // 0
count.value = 1
// Wait for 1 second...
console.log(count.value) // 1
在该例子中, useDebouncedRef
是一个使用 customRef
创建一个具有防抖动特性的自定义引用。这是通过设置一个延迟(delay
)来实现的,只有在过了这个延迟之后,新的值才会被赋给这个自定义引用,并触发响应。如果在这个延迟时间内,有新的值设置进来,那么就会清除上一次的定时器,然后重新设置一个新的定时器。
provide
和inject
的主要作用是去实现一种依赖注入功能,它允许开发者在一颗组件树中,从任意的祖先组件向其所有子孙组件注入一个依赖。
假设你有一个组件结构,根组件为father
,而father
包含了一个子组件child
,child
又包含了一个子组件sun
,而你想要把father
中的user
对象的name
属性传递给sun
组件。使用props
会非常繁琐,因为你需要在每个中间组件中不断的传递props
。如果是这样的情况,你应该使用provide/inject
。
provide
/inject
的使用方法如下:
father
组件),你可以在setup
中使用provide
来提供值:import { provide } from 'vue'
export default {
setup() {
const user = reactive({ name: 'Mike' })
provide('user', user)
}
}
sun
组件),使用inject
来注入上游组件通过provide
传递的值,并在该组件内部使用:import { inject } from 'vue'
export default {
setup() {
const user = inject('user')
return {
user
};
}
}
然后你就能在sun
组件中获取并使用user
对象的name
属性。
注意:
provide
和inject
能跨多级组件传递数据,避免了层层传递props的不便。provide
和inject
主要解决的是许多嵌套组件需要共享某些属性的问题,但并不推荐用得过于频繁,它更适合用来解决那些更为高级别的、跨越多层的共享问题。provide
和inject
没有react中context的那种动态订阅能力,即如果祖先组件provide的数据发生改变,所有子孙组件的值都会更新。provide
并没有自动可用的类型,因此推荐使用Symbol作为key来避免冲突。Vue3为我们提供了isRef
,isReactive
,isReadonly
,isProxy
一共4种函数,来判断一个对象是否为响应式数据。
isRef()
:用于判断一个对象是否经过 ref
处理后的响应式数据。举例:
import { ref, isRef } from "vue";
let a = ref(0);
isRef(a); // 返回true
let b = 0;
isRef(b); // 返回false
isReactive()
:用于判断一个对象是否经过 reactive
处理后的响应式数据。举例:
import { reactive, isReactive } from "vue";
let obj = reactive({ a: 1 });
isReactive(obj); // 返回true
let obj2 = { a: 1 };
isReactive(obj2); // 返回false
isReadonly()
:用于判断一个对象是否经过 readonly
处理后的响应式数据。举例:
import { readonly, isReadonly } from "vue";
let obj = readonly({ a: 1 });
isReadonly(obj); // 返回true
let obj2 = { a: 1 };
isReadonly(obj2); // 返回false
isProxy()
:用于判断一个对象是否经过 reactive
或 readonly
处理后的数据。举例:
import { reactive, readonly, isProxy } from "vue";
let obj = reactive({ a: 1 });
isProxy(obj); // 返回true
let obj2 = readonly({ a: 1 });
isProxy(obj2); // 返回true
let obj3 = { a: 1 };
isProxy(obj3); // 返回false
以上这四种方法主要就是检测不同类型的响应式对象,判断一个对象是否为某种类型的响应式对象。
总结:
好啦,以上我们详细探讨了Vue3中的一些新的响应式函数,包括shallowRef, shallowReactive, shallowReadonly等。这些函数为我们在搭建应用程序时提供了更多的选择和可能性。虽然它们的使用方式和传统的Vue响应式函数有所不同,但我们可以根据项目的具体需求选择最适合的那一个。通过理解这些函数的工作原理,我们可以更有效地利用Vue3来创建高性能的,响应式的应用程序。摄取这些知识需要时间和实践,充分利用这些新的响应式函数将极大地提升你的编程技能和提高应用程序的性能。