开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
我们在前面已经大致的了解了 vue2 和 vue3 的一些特性, 这里我们针对 vue3 的响应式原理 ,进行一下深度的剖析
- vue2 和 vue3 的响应式区别 和 api 使用的区别就不在这里复述了, 我们直接来看 vue3 的响应式的实现 ,下面是我自己写的一个 demo, 包含了 代码和 注释, 大家可以配合食用
//! 响应式原理 reactive 的实现 P106
const obj = {};
const proto = {bar:1};
const child = reactive(obj);
const parent = reactive(proto);
// 使用 parent 作为 child 的原型
Object.setPrototypeOf(child,parent);
effect(()=>{
console.log(child.bar); //1
})
// 修改 child.bar 的值
child.bar = 2 // 会执行两次副作用,原因是 set 的时候 child.bar parent.bar 都与副作用函数建立了联系,在赋值的时候, 调用 [[get]] 时,实际上是调用了两次get
function reactive(obj){
return new Proxy(obj,{
set(target,key,newVal,receiver){
const oldVal = target[key];
// 判断设置的属性是否是原有的,不是自身拥有的就是新增
const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD' ;
const res = Reflect.set(target,key,newVal,receiver);
// target === receiver.raw 说明 receiver 就是target 的代理对象, 如果 target 是沿着原型链找到的原型对象 如上面例子中的 parent, 则不等,这里 reveiver 一直都是代理对象 如 child
if(target === receiver.raw){
// && 后面用于判断 NaN 的情况,因为 NaN === NaN 为false
if(oldVal !== newVal && (oldVal === oldVal || newVal === newVal)){
tirgger(target,key,type)
}
}
return res
},
get(target,key,receiver){
// 代理对象可以通过raw属性 访问原始数据
if(key === 'raw'){
return target
}
track(target,key)
return Reflect.get(target,key,receiver)
}
})
}
// ref 源码 value 可以是 原始值,Object,Array,Map,Set等 实际上就是返回一个实例对象, 实例对象就是 RefImpl 类
function ref(value){
return createRef(value)
}
// shallow 表示是否是浅响应
function createRef(rawValue,shallow = false){
// 判断是否已是ref 对象
if(ifRef(rawValue)){
return rawValue
}
return new RefImpl(rawValue,shallow)
}
// 三点核心: value属性的getter,value属性的setter,shallow与convert
// convert 实现如下: 如果值是对象类型就会调用reactive实现完全代理 ,传入给ref的值是对象,实际上内部会调用reactive来实现完全代理,即value属性的值也会被代理,可以配置第二个参数shallow为 true,则不进行完全代理,只浅响应
const convert = (val) => isObject(val) ? reactive(val) : val;
class RefImpl{
constructor(_rawValue,_shallow){
this._rawValue = _rawValue; // 原始数据
this._shallow = _shallow; // 是否是浅层代理
this.__v_isRef = true; // 标记ref对象
this._value = _shallow ? _rawValue : convert(_rawValue); // 返回的 ref 对象
}
// 依赖追踪(收集依赖)对target进行toRaw操作,即始终使用目标代理的原始对象,而非代理对象。
get value(){
track(toRaw(this),'get' /* GET */, 'value')
return this._value
}
// 调用trigger函数来触发视图的更新(触发副作用),其次会替换内部属性_value的值为最新的值。
set value(newVal){
if(hasChanged(toRaw(newVal),this._rawValue)){
this._rawValue = newVal;
this._value = this._shallow ? newVal : convert(newVal);
tirgger(toRaw(this),'set' /* SET */, 'value', newVal)
}
}
}
// 收集依赖
function track()
// 派发更新
// toRef:可以用来为源响应式对象上的 property 创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接 语法是:toRef(Object, key) 实际上toRef只能用来对指定的对象属性创建一个ref对象。
// 如果对象属性的值本身就是ref对象就不会再次处理,否则会调用ObjectRefImpl类创建对应实例。ObjectRefImpl中不会对属性做任何操作,仅仅是定义value属性,而value属性的getter、setter只是简单的获取和赋值操作,toRef只是定义了value属性而已,之所以实现保持对 对象源属性的响应式连接,完全是属性所在对象自身必须是响应式
class ObjectRefImpl{
constructor(_object,_key){
this._object = _object;
this._key = _key;
this.__v_isRef = true;
}
get value(){
return this._object[this._key]
}
set value(newVal){
this._object[this._key] = newVal
}
}
function toRef(object,key){
return isRef(object[key]) ? object[key] : new ObjectRefImpl(object,key)
}
// toRefs: 就是批量的执行 toRef, 支持数组类型,只是浅层处理,只处理对象自身属性,不处理嵌套属性
function toRefs(object){
//object 必须是代理对象
if(!isProxy(object)){
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret = isArray(object) ? new Array(object.length) : { };
for(const key in object) {
ret[key] = toRef(object,key)
}
return ret
}
//! 自动脱ref , 如果读取的属性是一个ref,则直接将ref对应的value属性值返回,这样就完成了在模板中直接使用变量读取,不需要.value 来读取 ref, reactive 函数自身带有自动脱ref 的能力
function proxyRefs(target){
return new Proxy(target,{
get(target,key,receiver){
const value = Reflect.get(target,key,receiver);
// 自动脱 ref 实现: 如果读取的是 ref, 则返回他的 value 属性值,上面的 ref 实现中,每个ref都有一个ref属性标记 __v_isRef
return value.__v_isRef ? value.value : value
},
set(target,key,newVal,receiver){
// 通过 target 读取真实值
const value = target[key];
// 如果值是 Ref, 则设置其对应的value属性值
if(value.__v_isRef){
value.value = newVal
return true
}
return Reflect.set(target,key,newVal,receiver)
}
})
}