From d3d1a2c1759176b85e9dd47e184520a93e7c0272 Mon Sep 17 00:00:00 2001 From: * <*> Date: Tue, 23 Jan 2024 17:34:43 +0800 Subject: [PATCH] Match-id-9cf015cb84b803fe47e7daadbbd3a1ca9797d67e --- packages/inula-reactive/src/RNode.ts | 102 ++++------------ packages/inula-reactive/src/RNodeAccessor.ts | 21 +++- packages/inula-reactive/src/RNodeCreator.ts | 29 +++-- packages/inula-reactive/src/RProxyNode.ts | 115 ++++++++++++++++++ packages/inula-reactive/src/Utils.ts | 3 +- .../src/comparison/InDepthComparison.ts | 5 +- .../inula-reactive/src/proxy/RProxyHandler.ts | 3 +- .../inula-reactive/tests/reactive.test.ts | 44 ++++++- 8 files changed, 223 insertions(+), 99 deletions(-) create mode 100644 packages/inula-reactive/src/RProxyNode.ts diff --git a/packages/inula-reactive/src/RNode.ts b/packages/inula-reactive/src/RNode.ts index 1009d838..6bc02ac5 100644 --- a/packages/inula-reactive/src/RNode.ts +++ b/packages/inula-reactive/src/RNode.ts @@ -13,12 +13,6 @@ * See the Mulan PSL v2 for more details. */ -import { createProxy } from './proxy/RProxyHandler'; -import { getRNodeVal, setRNodeVal } from './RNodeAccessor'; -import { preciseCompare } from './comparison/InDepthComparison'; -import { isObject } from './Utils'; - - let runningRNode: RNode | undefined = undefined; // 当前正执行的RNode let calledGets: RNode[] | null = null; let sameGetsIndex = 0; // 记录前后两次运行RNode时,调用get顺序没有变化的节点 @@ -53,34 +47,21 @@ function defaultEquality(a: any, b: any) { } export class RNode { - private _value: T; - private fn?: () => T; - - // 维护数据结构 - root: Root | null; - parent: RNode | null = null; - key: KEY | null; - children: Map | null = null; - - proxy: any = null; - - extend: any; // 用于扩展,放一些自定义属性 + _value: T; + fn?: () => T; private observers: RNode[] | null = null; // 被谁用 private sources: RNode[] | null = null; // 使用谁 private state: State; private isEffect = false; - private isComputed = false; - private isProxy = false; + cleanups: ((oldValue: T) => void)[] = []; equals = defaultEquality; constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) { this.isEffect = options?.isEffect || false; - this.isProxy = options?.isProxy || false; - this.isComputed = options?.isComputed || false; if (typeof fnOrValue === 'function') { this.fn = fnOrValue as () => T; @@ -95,19 +76,6 @@ export class RNode { this._value = fnOrValue; this.state = Fresh; } - - // large object scene - if (this.isProxy) { - this.proxy = createProxy(this); - this.parent = options?.parent || null; - this.key = options?.key as KEY; - this.root = options?.root || null; - - if (this.parent && !this.parent.children) { - this.parent.children = new Map(); - this.parent.children.set(this.key, this); - } - } } get value(): T { @@ -119,6 +87,12 @@ export class RNode { } get(): T { + this.track(); + + return this.read(); + } + + track() { if (runningRNode) { // 前后两次运行RNode,从左到右对比,如果调用get的RNode相同就calledGetsIndex加1 if (!calledGets && runningRNode.sources && runningRNode.sources[sameGetsIndex] == this) { @@ -131,8 +105,6 @@ export class RNode { } } } - - return this.read(); } read(): T { @@ -154,39 +126,18 @@ export class RNode { const value = typeof fnOrValue === 'function' ? fnOrValue(prevValue) : fnOrValue; - const isObj = isObject(value); - const isPrevObj = isObject(prevValue); - - // 新旧数据都是 对象或数组 - if (isObj && isPrevObj) { - preciseCompare(this, value, prevValue, false); - - this.setDirty(); - - this.setValue(value); - } else { - if (!this.equals(prevValue, value)) { - this.setDirty(); - - this.setValue(value); - } - } + this.compare(prevValue, value); // 运行EffectQueue runEffects(); } - setByArrayModified(value: T) { - const prevValue = this.getValue(); + compare(prevValue: T, value: T) { + if (!this.equals(prevValue, value)) { + this.setDirty(); - preciseCompare(this, value, prevValue, true); - - this.setDirty(); - - this.setValue(value); - - // 运行EffectQueue - runEffects(); + this.setValue(value); + } } setDirty() { @@ -215,7 +166,7 @@ export class RNode { } } - private update(): void { + update(): void { const prevValue = this.getValue(); const prevReaction = runningRNode; @@ -233,11 +184,7 @@ export class RNode { } // 执行 reactive 函数 - if (this.isComputed) { - this.root = { $: this.fn!() }; - } else { - this._value = this.fn!(); - } + this.execute(); if (calledGets) { // remove all old sources' .observers links to us @@ -285,6 +232,10 @@ export class RNode { this.state = Fresh; } + execute() { + // 执行 reactive 函数 + this._value = this.fn!(); + } /** * 1、如果this是check,就去找dirty的parent @@ -321,17 +272,14 @@ export class RNode { } } - private getValue() { - return this.isProxy ? getRNodeVal(this) : this._value; + getValue() { + return this._value; } - private setValue(value: any) { - this.isProxy ? setRNodeVal(this, value) : (this._value = value); + setValue(value: any) { + this._value = value; } - private qupdate() { - - } } export function onCleanup(fn: (oldValue: T) => void): void { diff --git a/packages/inula-reactive/src/RNodeAccessor.ts b/packages/inula-reactive/src/RNodeAccessor.ts index 9392346e..4d062642 100644 --- a/packages/inula-reactive/src/RNodeAccessor.ts +++ b/packages/inula-reactive/src/RNodeAccessor.ts @@ -13,11 +13,10 @@ * See the Mulan PSL v2 for more details. */ -import { RNode } from './RNode'; import { isFunction } from './Utils'; +import { RProxyNode } from './RProxyNode'; - -export function getRNodeVal(node: RNode): any { +export function getRNodeVal(node: RProxyNode): any { let currentNode = node; const keys: (string | symbol)[] = []; while (currentNode.key !== null && currentNode.parent !== null) { @@ -35,7 +34,7 @@ export function getRNodeVal(node: RNode): any { return rawObj; } -export function setRNodeVal(rNode: RNode, value: unknown): void { +export function setRNodeVal(rNode: RProxyNode, value: unknown): void { const parent = rNode.parent; const key = rNode.key!; const isRoot = parent === null; @@ -54,12 +53,22 @@ export function setRNodeVal(rNode: RNode, value: unknown): void { } } -export function setExtendProp(rNode: RNode, key: string, value: any) { +export function getRootRNode(node: RProxyNode): RProxyNode { + let currentNode = node; + const keys: (string | symbol)[] = []; + while (currentNode.parent !== null) { + currentNode = currentNode.parent; + } + + return currentNode; +} + +export function setExtendProp(rNode: RProxyNode, key: string, value: any) { rNode.extend = rNode.extend || {}; rNode.extend[key] = value; } -export function getExtendProp(rNode: RNode, key: string, defaultValue: any) { +export function getExtendProp(rNode: RProxyNode, key: string, defaultValue: any) { rNode.extend = rNode.extend || {}; if (key in rNode.extend) { return rNode.extend[key]; diff --git a/packages/inula-reactive/src/RNodeCreator.ts b/packages/inula-reactive/src/RNodeCreator.ts index 648565e5..ea3c57bb 100644 --- a/packages/inula-reactive/src/RNodeCreator.ts +++ b/packages/inula-reactive/src/RNodeCreator.ts @@ -16,6 +16,8 @@ import { isPrimitive } from './Utils'; import { RNode } from './RNode'; import { ProxyRNode } from './Types'; +import { RProxyNode } from './RProxyNode'; +import {getRNodeVal, getRootRNode} from "./RNodeAccessor"; export type Reactive = RNode | Atom; @@ -23,8 +25,7 @@ export function createReactive(raw?: T): ReactiveProxy { if (isPrimitive(raw) || raw === null || raw === undefined) { return new RNode(raw, { isSignal: true }); } else { - const node = new RNode(null, { - isProxy: true, + const node = new RProxyNode(null, { root: { $: raw }, }); return node.proxy as ReactiveProxy; @@ -32,7 +33,7 @@ export function createReactive(raw?: T): ReactiveProxy { } export function createComputed(fn: T) { - const rNode = new RNode(fn, { isProxy: true, isComputed: true }); + const rNode = new RProxyNode(fn, { isComputed: true }); return rNode.proxy; } @@ -44,23 +45,37 @@ export function createWatch(fn: T) { rNode.get(); } -export function getOrCreateChildProxy(value: unknown, parent: RNode, key: string | symbol): ProxyRNode { +export function getOrCreateChildProxy(value: unknown, parent: RProxyNode, key: string | symbol): ProxyRNode { const child = getOrCreateChildRNode(parent, key); return child.proxy; } -export function getOrCreateChildRNode(node: RNode, key: string | symbol): RNode { +export function getOrCreateChildRNode(node: RProxyNode, key: string | symbol): RProxyNode { let child = node.children?.get(key); if (!child) { - child = new RNode(null, { - isProxy: true, + // child = new RProxyNode(null, { + // parent: node, + // key: key, + // root: node.root, + // }); + + child = new RProxyNode(() => { + const rootRNode = getRootRNode(node); + // 依赖根 + rootRNode.get(); + + return getRNodeVal(node)[key]; + }, { + isComputed: true, parent: node, key: key, root: node.root, }); } + child.track(); + return child; } diff --git a/packages/inula-reactive/src/RProxyNode.ts b/packages/inula-reactive/src/RProxyNode.ts new file mode 100644 index 00000000..b1a03545 --- /dev/null +++ b/packages/inula-reactive/src/RProxyNode.ts @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { createProxy } from './proxy/RProxyHandler'; +import {getRNodeVal, getRootRNode, setRNodeVal} from './RNodeAccessor'; +import { preciseCompare } from './comparison/InDepthComparison'; +import { isObject } from './Utils'; +import {RNode, Root, runEffects} from "./RNode"; + +export interface RNodeOptions { + root?: Root | null; + isSignal?: boolean; + isEffect?: boolean; + isComputed?: boolean; + isProxy?: boolean; + parent?: RNode | null; + key?: KEY | null; + equals?: (a: any, b: any) => boolean; +} + +export type KEY = string | symbol; + +export class RProxyNode extends RNode { + // 维护数据结构 + root: Root | null; + parent: RProxyNode | null = null; + key: KEY | null; + children: Map | null = null; + + proxy: any = null; + + extend: any; // 用于扩展,放一些自定义属性 + + isComputed = false; + + constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) { + super(fnOrValue, options); + + this.isComputed = options?.isComputed || false; + + this.proxy = createProxy(this); + this.parent = options?.parent || null; + this.key = options?.key as KEY; + this.root = options?.root || {}; + + if (this.parent && !this.parent.children) { + this.parent.children = new Map(); + this.parent.children.set(this.key, this); + } + + if (this.isComputed) { + this.update(); + } + } + + compare(prevValue: any, value: any) { + const isObj = isObject(value); + const isPrevObj = isObject(prevValue); + + // 新旧数据都是 对象或数组 + if (isObj && isPrevObj) { + // preciseCompare(this, value, prevValue, false); + + this.setDirty(); + + this.setValue(value); + } else { + if (!this.equals(prevValue, value)) { + this.setDirty(); + + this.setValue(value); + } + } + } + + execute() { + // 执行 reactive 函数 + if (this.isComputed) { + setRNodeVal(this, this.fn!()); + } + } + + setByArrayModified(value: T) { + const prevValue = this.getValue(); + + preciseCompare(this, value, prevValue, true); + + this.setDirty(); + + this.setValue(value); + + // 运行EffectQueue + runEffects(); + } + + getValue() { + return getRNodeVal(this); + } + + setValue(value: any) { + setRNodeVal(this, value); + } +} diff --git a/packages/inula-reactive/src/Utils.ts b/packages/inula-reactive/src/Utils.ts index a547c5b2..07ed92bb 100644 --- a/packages/inula-reactive/src/Utils.ts +++ b/packages/inula-reactive/src/Utils.ts @@ -14,9 +14,10 @@ */ import { RNode } from './RNode'; +import { RProxyNode } from './RProxyNode'; export function isReactiveObj(obj: any) { - return obj instanceof RNode; + return obj instanceof RNode || obj instanceof RProxyNode; } export function isObject(obj: unknown): boolean { diff --git a/packages/inula-reactive/src/comparison/InDepthComparison.ts b/packages/inula-reactive/src/comparison/InDepthComparison.ts index 2a4d65d3..130a796f 100644 --- a/packages/inula-reactive/src/comparison/InDepthComparison.ts +++ b/packages/inula-reactive/src/comparison/InDepthComparison.ts @@ -19,9 +19,10 @@ import { ArrayState } from '../Types'; import { getOrCreateChildRNode } from '../RNodeCreator'; import { RNode } from '../RNode'; import {getExtendProp, setExtendProp} from "../RNodeAccessor"; +import {RProxyNode} from "../RProxyNode"; // 递归触发依赖这reactive数据的所有RContext -export function preciseCompare(rNode: RNode, value: any, prevValue: any, isFromArrModify?: boolean) { +export function preciseCompare(rNode: RProxyNode, value: any, prevValue: any, isFromArrModify?: boolean) { preciseCompareChildren(rNode, value, prevValue, isFromArrModify); // 触发父数据的RContext,不希望触发组件刷新(只触发computed和watch) @@ -30,7 +31,7 @@ export function preciseCompare(rNode: RNode, value: any, prevValue: any, is } // 当value和prevValue都是对象或数组时,才触发 -function preciseCompareChildren(rNode: RNode, value: any, prevValue: any, isFromArrModify?: boolean): boolean { +function preciseCompareChildren(rNode: RProxyNode, value: any, prevValue: any, isFromArrModify?: boolean): boolean { // 可以精准更新 let canPreciseUpdate = true; diff --git a/packages/inula-reactive/src/proxy/RProxyHandler.ts b/packages/inula-reactive/src/proxy/RProxyHandler.ts index e9344a14..9771fcad 100644 --- a/packages/inula-reactive/src/proxy/RProxyHandler.ts +++ b/packages/inula-reactive/src/proxy/RProxyHandler.ts @@ -17,6 +17,7 @@ import { getOrCreateChildProxy } from '../RNodeCreator'; import { getRNodeVal } from '../RNodeAccessor'; import { isArray } from '../Utils'; import { RNode } from '../RNode'; +import {RProxyNode} from "../RProxyNode"; const GET = 'get'; const SET = 'set'; @@ -57,7 +58,7 @@ const FNS: Record { const c = computed(() => { callCount++; - return a.get() * b.get(); + return { a: a.get() * b.get() }; }); watch(() => { @@ -18,14 +18,14 @@ describe('test reactive', () => { expect(a.read()).toBe(7); a.set(2); - expect(c.read()).toBe(2); + expect(c.a.read()).toBe(2); b.set(3); - expect(c.get()).toBe(6); + expect(c.a.get()).toBe(6); - expect(callCount).toBe(2); + expect(callCount).toBe(3); c.read(); - expect(callCount).toBe(2); + expect(callCount).toBe(3); }); it('reactive is a obj', () => { @@ -65,4 +65,38 @@ describe('test reactive', () => { expect(doubleId.get()).toBe(22); }); + + it('return obj computed', () => { + const a = reactive(7); + + const c = computed(() => { + return { a: a.get() }; + }); + + a.set(2); + expect(c.a.read()).toBe(2); + + }); + + it('reactive is a array, watch', () => { + const rObj = reactive({ + items: [ + { name: 'p1', id: 1 }, + { name: 'p2', id: 2 }, + ], + }); + + watch(() => { + console.log(rObj.items[0].id.get()); + }); + + rObj.items.set([ + { name: 'p1', id: 1 }, + { name: 'p2', id: 2 }, + ]); + + rObj.items.set([{ name: 'p11', id: 11 }]); + + rObj.items[0].id.set(111); + }); });