mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] reactivity: fix issues with reactive objects in proto chain
Previously, when there was a reactive object in the prototype chain of a different object, it would get notified of the first write to a property that existed on the reactive object but did not yet exist on the other object, despite the value of that property not actually getting written to the reactive and hence the value not getting changed. This was caused by the fact that we assumed that Reflect.set would set the value on the target, when in fact, the value is set on the object that underlies the `receiver`. When a reactive object is part of the prototype chain, the first write on a key triggers the set trap but the receiver is not the reactive object, and so Reflect.set doesn't modify the target, but adds the new key to the object that's lower in the prototype chain. This commit fixes that by actually reading the value from the target instead of assuming that it was changed by Reflect.set This commit also removes the special symbols SKIP and TARGET, as there is no reliable way to check whether these keys are present on the object itself or on its prototype chain, which can cause issue when trying to create reactive objects from objects with other reactive objects in their prototype chain, or to create reactive objects from objects with non-reactive objects in their prototype chain. To solve this problem, we simply use a WeakMap that maps reactives to their targets, and a WeakSet that contains all skipped objects.
This commit is contained in:
committed by
Géry Debongnie
parent
4a5316ced5
commit
f6e8aff725
@@ -9,7 +9,7 @@ import {
|
||||
markRaw,
|
||||
toRaw,
|
||||
} from "../src";
|
||||
import { reactive, Reactive } from "../src/runtime/reactivity";
|
||||
import { reactive } from "../src/runtime/reactivity";
|
||||
import { batched } from "../src/runtime/utils";
|
||||
import {
|
||||
makeDeferred,
|
||||
@@ -1142,6 +1142,46 @@ describe("Reactivity", () => {
|
||||
expect(() => state.a).not.toThrow();
|
||||
expect(state.a).toBe(obj.a);
|
||||
});
|
||||
|
||||
test("writing on object with reactive in prototype chain doesn't notify", async () => {
|
||||
let n = 0;
|
||||
const state = createReactive({ val: 0 }, () => n++);
|
||||
const nonReactive = Object.create(state);
|
||||
nonReactive.val++;
|
||||
expect(n).toBe(0);
|
||||
expect(toRaw(state)).toEqual({ val: 0 });
|
||||
expect(toRaw(nonReactive)).toEqual({ val: 1 });
|
||||
state.val++;
|
||||
expect(n).toBe(1);
|
||||
expect(toRaw(state)).toEqual({ val: 1 });
|
||||
expect(toRaw(nonReactive)).toEqual({ val: 1 });
|
||||
});
|
||||
|
||||
test("creating key on object with reactive in prototype chain doesn't notify", async () => {
|
||||
let n = 0;
|
||||
const parent = createReactive({}, () => n++);
|
||||
const child = Object.create(parent);
|
||||
Object.keys(parent); // Subscribe to key changes
|
||||
child.val = 0;
|
||||
expect(n).toBe(0);
|
||||
});
|
||||
|
||||
test("reactive of object with reactive in prototype chain is not the object from the prototype chain", async () => {
|
||||
const cb = () => {};
|
||||
const parent = createReactive({ val: 0 }, cb);
|
||||
const child = createReactive(Object.create(parent), cb);
|
||||
expect(child).not.toBe(parent);
|
||||
});
|
||||
|
||||
test("can create reactive of object with non-reactive in prototype chain", async () => {
|
||||
let n = 0;
|
||||
const parent = markRaw({ val: 0 });
|
||||
const child = createReactive(Object.create(parent), () => n++);
|
||||
child.val++;
|
||||
expect(n).toBe(1);
|
||||
expect(parent).toEqual({ val: 0 });
|
||||
expect(child).toEqual({ val: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Collections", () => {
|
||||
@@ -1699,12 +1739,12 @@ describe("toRaw", () => {
|
||||
const obj = { value: 1 };
|
||||
const reactiveObj = reactive(obj);
|
||||
expect(reactiveObj).not.toBe(obj);
|
||||
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
|
||||
expect(toRaw(reactiveObj)).toBe(obj);
|
||||
});
|
||||
|
||||
test("giving a non reactive to toRaw return the object itself", () => {
|
||||
const obj = { value: 1 };
|
||||
expect(toRaw(obj as Reactive<typeof obj>)).toBe(obj);
|
||||
expect(toRaw(obj)).toBe(obj);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user