[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:
Samuel Degueldre
2022-11-29 10:03:08 +01:00
committed by Géry Debongnie
parent 4a5316ced5
commit f6e8aff725
4 changed files with 81 additions and 59 deletions
+4 -11
View File
@@ -3,14 +3,7 @@ import { BDom, VNode } from "./blockdom";
import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError, OwlError } from "./error_handling";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import {
clearReactivesForCallback,
getSubscriptions,
NonReactive,
Reactive,
reactive,
TARGET,
} from "./reactivity";
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
import { STATUS } from "./status";
import { batched, Callback } from "./utils";
@@ -52,7 +45,7 @@ const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
export function useState<T extends object>(state: T): T {
const node = getCurrent();
let render = batchedRenderFunctions.get(node)!;
if (!render) {
@@ -116,7 +109,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.childEnv = env;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && prop[TARGET]) {
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}
@@ -240,7 +233,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
currentNode = this;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && prop[TARGET]) {
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}