Vue 3.x 响应式:进阶
shallowReadonly()
readonly()
的浅层作用形式,和 readonly()
不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
function shallowReadonly<T extends object>(target: T): Readonly<T>
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性会失败
state.foo++
// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false
// 这是可以通过的
state.nested.bar++
markRaw()
将一个对象标记为不可被转为响应式。返回该对象本身。
function markRaw<T extends object>(value: T): T
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
使用原因:
- 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。
只在根层访问能到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:
const foo = markRaw({
nested: {}
})
const bar = reactive({
// 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
nested: foo.nested
})
console.log(foo.nested === bar.nested) // false
EffectScope 副作用域
interface EffectScope {
run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
stop(): void
}
function effectScope(detached?: boolean): EffectScope
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count: ', doubled.value))
})
// 停用掉当前作用域内的所有 effect
// 当 scope.stop () 被调用时,它将递归地停止所有 捕获的 effects 和 嵌套的 scopes。
scope.stop()
console.log(scope.run(() => 1)) // 1 run 方法还转发已执行函数的返回值
EffectScope 还能再嵌套
嵌套作用域由其父作用域收集。当父作用域被释放时,它的所有子作用域也将被停止,除非使用 detached
分离参数。
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
effectScope().run(() => {
watch(doubled, () => console.log(doubled.value))
})
watchEffect(() => console.log('Count: ', doubled.value))
})
// 停用所有 effects, 包括嵌套作用域中的 effects
scope.stop()
使用 detached
分离参数
let nestedScope
const parentScope = effectScope()
parentScope.run(() => {
const doubled = computed(() => counter.value * 2)
// 不被父作用域收集和处理
nestedScope = effectScope(true /* detached */)
nestedScope.run(() => {
watch(doubled, () => console.log(doubled.value))
})
watchEffect(() => console.log('Count: ', doubled.value))
})
// 停用所有 effects, 但不包括 nestedScope
parentScope.stop()
// 停用 nestedScope
nestedScope.stop()
getCurrentScope()
返回当前活跃的 effect 作用域
function getCurrentScope(): EffectScope | undefined
import { getCurrentScope } from 'vue'
getCurrentScope() // EffectScope | undefined
onScopeDispose()
当相关的 effect 作用域停止时会调用这个回调函数,可以作为可复用的组合式函数中 onUnmounted
的替代品。
它并不与组件耦合,因为每一个 Vue 组件的 setup()
函数也是在一个 effect 作用域中调用的。
function onScopeDispose(fn: () => void): void
全局钩子 onScopeDispose ()
具有与 onUnmount ()
类似的功能,但是它适用于当前作用域而不是组件实例。这有利于可组合函数清除它们的副作用及其范围。由于 setup () 也为组件创建了一个作用域,所以当没有创建显式 effects 作用域时,它将等效于 onUnmount ()
。
import { onScopeDispose } from 'vue'
const scope = effectScope()
scope.run(() => {
onScopeDispose(() => {
console.log('cleaned!')
})
})
scope.stop() // logs 'cleaned!'
示例
function useMouse() {
const x = ref(0)
const y = ref(0)
function handler(e) {
x.value = e.x
y.value = e.y
}
window.addEventListener('mousemove', handler)
//onUnmounted(() => {
onScopeDispose(() => {
window.removeEventListener('mousemove', handler)
})
return { x, y }
}
export default {
setup() {
const enabled = ref(false)
let mouseState, mouseScope
const dispose = () => {
mouseScope && mouseScope.stop()
mouseState = null
}
watch(
enabled,
() => {
if (enabled.value) {
mouseScope = effectScope()
mouseState = mouseScope.run(() => useMouse())
} else {
dispose()
}
},
{ immediate: true }
)
onScopeDispose(dispose) // 这仍然有效,因为 Vue 组件现在还在作用域内运行它的 setup(),当组件卸载时将释放这个作用域。
},
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Vue 3.x 响应式:工具
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论