-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
readonly() breaks comparison between objects #4986 #1
Comments
投资学习是不会亏本的,为了心中的 M1 MAX 🌹 |
我先来开个头,首先调试这个 issue 源码执行对应的步骤有两种方法(我能想到的
目标,进入到依赖 vue 中查看对应的源码。
具体方法参考若川巨神的文章 https://juejin.cn/post/6994976281053888519#heading-4
具体方法参考 |
👍 Work hard, study harder. |
测试代码问题出现的原因将readonly对象r复制给rr.foo丢失了readonly属性 代码沙箱 import { reactive, readonly, isReadonly } from "vue"; //vue 3.2.21
const r = readonly({});
const rr = reactive({});
rr.foo = r;
console.log("equal: ", rr.foo === r); //equal: false
console.log("Before isReadonly:", isReadonly(r));//Before isReadonly:true
// 问题出在readonly属性
console.log("After isReadonly:", isReadonly(rr.foo)); //After isReadonly:false vscode上调试
修复原理function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow && !isReadonly(value)) {
//如果不是readonly对象,参数value通过toRaw重设为原始对象
value = toRaw(value)
oldValue = toRaw(oldValue)
}
//如果是readonly对象,参数value保持不变
const result = Reflect.set(target, key, value, receiver)
return result
}
} 闭包的运用,以isReadonly为例创建readonly对象 const r = readonly({}); 调用createReactiveObject,传入isReadonly为true,collectionHandlers为readonlyHandlers export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,//只读
readonlyHandlers,//只读对象Handlers
readonlyCollectionHandlers,//只读集合Handlers
readonlyMap
)
} createReactiveObject返回一个新的Proxy对象 function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
const proxy = new Proxy(
target,
baseHandlers
)
return proxy
} 代理对象get通过createGetter(true)返回一个get函数 export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
}
const readonlyGet = /*#__PURE__*/ createGetter(true)
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
} isReadonly通过代理调用了get,返回了之前通过闭包保存的isReadonly值true export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
} |
题目理解:当我的的reactive对象的属性值赋值readonly类型的值的时候,那么reactive对象的属性值应该保留readonly的proxy 问题诊断:可以打印出两个属性值的 console.log("moreComplex.a", moreComplex.a.__v_isReadonly); // 3.2.21 => false, // 修复版本 => true
console.log("simpler.a", simpler.a.__v_isReadonly); // 3.2.21 => true, // 修复版本 => true 出现问题的原因:在 3.2.21版本中,处理reactive的set的时候没有过滤value是readonly类型的情况,所以给value赋值了readonly数据的raw类型,代码如下 function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value) // 当readonly数据类型过来的时候,找到源数据进行赋值
oldValue = toRaw(oldValue)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
} 问题修复:在修复版本,加上了给reactive数据类型赋值readonly类型的处理 function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow && !isReadonly(value)) { // 对属性值是readonly类型的数据进行了过滤
value = toRaw(value)
oldValue = toRaw(oldValue)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
|
为了我的奥迪,为了媳妇儿的迪奥,开始卷,xdm,fighing!🌹 |
issues 标号 #4986
问题class Type {
constructor(code) {
this.code = code;
}
getCode() {
return this.code;
}
}
let simpler = readonly({ a: new Type(0), b: new Type(1) });
let moreComplex = reactive({ a: simpler.a, b: simpler.b });
console.log(`Before: ${moreComplex.a === simpler.a}`);
moreComplex.a = simpler.b;
moreComplex.a = simpler.a;
console.log(`After: ${moreComplex.a === simpler.a}`); 这是为什么呢,通过反复赋值,就不相等了?
|
test('setting a readonly object as a property of a reactive object should retain readonly proxy', () => {
const r = readonly({})
const rr = reactive({}) as any
rr.foo = r
expect(rr.foo).toBe(r)
expect(isReadonly(rr.foo)).toBe(true)
}) rr.foo = r 这一步赋值了一个readonly属性的值而丢失了readonly属性 function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
oldValue = toRaw(oldValue)
}
const result = Reflect.set(target, key, value, receiver)
return result
}
} 赋值出发了set value = toRaw(value) value通过toRaw重设为原始对象,所以要对readonly属性进行过滤 function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
// 过滤
if (!shallow && !isReadonly(value)) {
value = toRaw(value)
oldValue = toRaw(oldValue)
}
const result = Reflect.set(target, key, value, receiver)
return result
}
} |
解决步骤
解决方案 在 set 的时候,如果是 readonly 就不转换成普通变量 |
调试过程:import { reactive, readonly, isReactive, isReadonly } from "vue"; //vue 3.2.21 class Type { getCode() { const simpler = readonly({ a: new Type(0), b: new Type(1) }); moreComplex.a = simpler.b; console.log(moreComplex.a); // { code: 1 } console.log(isReactive(moreComplex.a)); // true moreComplex.a = simpler.a; console.log(moreComplex.a); // { code: 0 } console.log(isReactive(moreComplex.a)); // true console.log( 从第一次赋值的时候就会触发set,set 里面会有一个调用 toRaw 函数的动作,所以这里就会把 .a 转换成普通的值对象,然后触发 get,从普通对象转成 reactive 对象了。最终判断的两者不相等只是类型不同,但是值仍然是相等的。 |
readonly() breaks comparison between objects #4986
为什么要读他
可以学到什么
视频讲解
【共读 Vue3】如何测试代码的执行速度
开始时间
2021-12-01
12月第一周
The text was updated successfully, but these errors were encountered: