vue2 与 vue3 的响应式实现
在我印象中, vue2 使用的是defineProperty
, vue3 使用的是一个proxy
. 翻了文档才发现我错了.
vue2
众所周知, vue2 基本上都是选项式 API, 需要的响应式数据都写在 data 函数里, 这个函数返回一个对象. 因此只需要让这个对象具有响应式就可以了. 同时需要注意让data里的对象和数组都要有响应式.
function makeReactive(obj) {
if (typeof obj !== 'object' || obj === null) {
// 不是对象就抛错
throw new Error('need an object');
}
Object.keys(obj).forEach((key) => {
let originValue = obj[key];
if (typeof originValue === 'object' && originValue !== null) {
obj[key] = makeReactive(originValue);
}
Object.defineProperty(obj, key, {
get() {
console.log(`Getting ${key}: ${originValue}`);
return originValue;
},
set(newValue) {
console.log(`Setting ${key} to ${newValue}`);
console.log(typeof newValue === 'object' && newValue !== null);
if (typeof newValue === 'object' && newValue !== null) {
obj[key] = makeReactive(newValue);
} else {
originValue = newValue;
}
},
});
});
}
这样其实会有一些问题. 很难追踪到data里某个对象增加了一个key-value, 也很难追踪某个数组的push, pop等. 这是因为在响应式化的时候是通过遍历所有属性赋予响应式导致的, 之前没出现过的属性当然就跟踪不了.
同时也会产生一个效率问题, 那就是大数组的时候. 假如有100w的数组, 那么我就需要遍历100w次, 这太恐怖了. 对此有一个解决方案, 使用冻结对象, 这样vue2就不会尝试响应式化了. 或者明明以_开头.
vue3
众所周知, vue3提供了ref和reactive两种. 它们还是有点区别的.
reactive用于对象(当然也包括数组), 它使用的是proxy.
function reactive(obj) {
if(typeof obj !== 'object' || obj === null) {
throw new Error('need an object')
}
return new Proxy(obj, {
get(target, key) {
// 收集依赖
return target[key]
},
set(target, key, value) {
// 触发事件
target[key] = value
return true
}
})
}
这里只是简单实现, 实际可能更复杂. 简单对比一下就可以发现:
不需要遍历, 性能更好
即使之前没有出现过的属性也有响应式
这里其实还有一点优化, 就是访问子对象时才会创建一个proxy而不是立刻递归地创建.
ref主要用在普通的值, 比如数字, 字符串等. 像这种主要对值访问而不是某一个key-value访问的属性用proxy就不太方便了.
class RefImf {
_value
constructor(value) {
this._value = value
}
get value() {
// 收集依赖
return value
}
set value(value) {
// 触发事件
this._value = value
}
}
function ref(value) {
return new RefImf(value)
}
简单使用getter/setter拦截器就可以实现值的响应式了.
平时我们给ref传一个object时, 用的就不是这个getter/setter了, 而是使用proxy, vue3会帮我们处理好.
ref和reactive的区别
区别在于使用ref的话之后需要.value才能获取值, 使用reactive可以不需要.value.
同时ref允许传递任意值, reactive只允许传递对象.
后言
怎么说呢, 有点想自己写一遍vue, 我感觉我很难再有所突破了, 如果每天只是写写小页面. 使用echarts, canvas, three.js也会给我带来提升, 不过怎么说呢, 我个人不太喜欢这些东西, 我也没有设计审美, 不知道用这些东西做什么.
我大概会开一个仓库复现vue的一些东西吧, 不知道能坚持到什么时候.