响应式系统边界情况处理
响应式系统边界情况处理
Vue3的响应式系统基于Proxy实现,相比Vue2的Object.defineProperty有更强的能力,但在某些特殊场景下仍需要特殊处理。这些边界情况包括原始值响应式、数组操作、属性删除、NaN处理等。
原始值的响应式处理
Proxy无法直接代理原始值(string/number/boolean等),Vue3通过ref()函数对其进行包装:
const count = ref(0) // 实际创建的是 { value: 0 }的响应式对象
// 模板中使用会自动解包
// <div>{{ count }}</div> 无需写count.value
对于原始值的响应式更新需要特别注意:
let state = reactive({
primitive: 'initial' // 原始值属性
})
// 直接替换会失去响应性
state.primitive = 'new value' // 有效
state = { primitive: 'another' } // 无效,因为重新赋值了整个state对象
数组的特殊处理
虽然Proxy可以检测数组索引变化,但某些操作仍需特殊处理:
const arr = reactive([1, 2, 3])
// 这些方法能触发响应
arr.push(4)
arr.splice(0, 1)
// 直接设置length不会触发响应
arr.length = 0 // 不会触发更新
// 应该使用
arr.splice(0) // 会触发更新
对于稀疏数组的处理:
const sparse = reactive([])
sparse[100] = 'value' // 能触发响应,但Vue会创建100个空项
// 更推荐的做法
sparse.splice(100, 1, 'value')
属性删除的响应式
使用delete操作符删除属性时:
const obj = reactive({ prop: 'value' })
delete obj.prop // 能触发响应
但对于Map/Set等集合类型:
const map = reactive(new Map())
map.delete('key') // 不会触发响应
// 正确做法
const map = reactive(new Map())
set(() => map.delete('key')) // 需要包裹在effect中
NaN的相等性比较
JavaScript中NaN !== NaN,这会导致响应式系统的问题:
const state = reactive({ value: NaN })
watch(() => state.value, (newVal) => {
console.log('changed:', newVal)
})
state.value = NaN // 仍会触发,因为Vue内部做了特殊处理
循环引用的处理
当对象存在循环引用时:
const obj = reactive({})
obj.self = obj // 创建循环引用
// Vue能正确处理这种情况
console.log(obj.self.self === obj) // true
但在转换为原始对象时需要注意:
const plain = JSON.parse(JSON.stringify(obj)) // 会抛出循环引用错误
不可配置属性的处理
当对象属性被设置为不可配置(configurable: false)时:
const obj = {}
Object.defineProperty(obj, 'prop', {
value: 'static',
configurable: false
})
const reactiveObj = reactive(obj)
// 尝试修改会静默失败
reactiveObj.prop = 'new value' // 无效
原型链属性的响应式
原型链上的属性默认不会被代理:
const parent = { parentProp: 'value' }
const child = reactive(Object.create(parent))
console.log(child.parentProp) // 'value'
child.parentProp = 'new' // 不会触发响应,因为操作的是原型链
异步更新队列的边界情况
在同一个事件循环中的多次修改:
const state = reactive({ count: 0 })
state.count++
state.count++
// 最终只会触发一次更新
如果需要强制同步更新:
import { flushSync } from 'vue'
flushSync(() => {
state.count++
state.count++
}) // 会触发两次更新
响应式对象的标记
Vue内部使用__v_skip标记跳过响应式转换:
const obj = { __v_skip: true }
const reactiveObj = reactive(obj) // 不会进行代理,直接返回原对象
大型数组的性能优化
处理大型数组时,直接响应式代理会有性能问题:
const largeArray = reactive(new Array(1000000).fill(0))
// 更高效的只读处理
const readonlyArray = readonly(largeArray) // 不会创建每个元素的代理
自定义响应式行为
通过自定义ref实现特殊响应逻辑:
function customRef(factory) {
let value
return {
get() {
track(this, 'value')
return value
},
set(newVal) {
value = factory(newVal)
trigger(this, 'value')
}
}
}
const age = customRef((val) => {
return Math.max(0, Math.min(120, val)) // 限制0-120岁
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:手动停止响应的方法
下一篇:与Vue2响应式系统的对比