From 1cdaf1bdc8c718099e40717d50ba78d63627788e Mon Sep 17 00:00:00 2001 From: Hoikan <30694822+HoikanChan@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:30:34 +0800 Subject: [PATCH] fix(reactive): fix type error --- packages/inula-reactive/package.json | 4 +- packages/inula-reactive/src/RNode.ts | 44 ++-- packages/inula-reactive/src/RNodeCreator.ts | 11 +- packages/inula-reactive/src/RProxyNode.ts | 44 ++-- packages/inula-reactive/src/SetScheduler.ts | 36 +++ packages/inula-reactive/src/Types.ts | 77 +----- packages/inula-reactive/src/Utils.ts | 2 +- .../src/comparison/ArrayDiff.ts | 177 ------------- .../src/comparison/InDepthComparison.ts | 234 ------------------ packages/inula-reactive/src/index.ts | 11 +- .../inula-reactive/src/proxy/RProxyHandler.ts | 13 +- packages/inula-reactive/tsup.config.js | 1 + 12 files changed, 120 insertions(+), 534 deletions(-) create mode 100644 packages/inula-reactive/src/SetScheduler.ts delete mode 100644 packages/inula-reactive/src/comparison/ArrayDiff.ts delete mode 100644 packages/inula-reactive/src/comparison/InDepthComparison.ts diff --git a/packages/inula-reactive/package.json b/packages/inula-reactive/package.json index 01d1256b..649f8acd 100644 --- a/packages/inula-reactive/package.json +++ b/packages/inula-reactive/package.json @@ -6,6 +6,7 @@ "type": "module", "scripts": { "build": "tsup", + "dev": "tsup --watch", "test": "jest --config=jest.config.js", "bench": "node --experimental-specifier-resolution=node --inspect ./bench/index.js" }, @@ -16,7 +17,8 @@ "devDependencies": { "chalk": "^5.3.0", "cli-table": "^0.3.11", + "esbuild-plugin-replace": "^1.4.0", "tsup": "^6.7.0", - "esbuild-plugin-replace": "^1.4.0" + "tslib": "^2.6.2" } } diff --git a/packages/inula-reactive/src/RNode.ts b/packages/inula-reactive/src/RNode.ts index a6a0f18f..c6f77deb 100644 --- a/packages/inula-reactive/src/RNode.ts +++ b/packages/inula-reactive/src/RNode.ts @@ -16,6 +16,7 @@ import { printNode } from './utils/printNode'; import { Signal } from './Types'; import { isFunction } from './Utils'; +import { schedule } from './SetScheduler'; let runningRNode: RNode | undefined = undefined; // 当前正执行的RNode let calledGets: RNode[] | null = null; @@ -32,18 +33,14 @@ type NonClean = typeof Check | typeof Dirty; export interface RNodeOptions { isSignal?: boolean; isEffect?: boolean; + // effect is lazy by default + lazy?: boolean; isComputed?: boolean; isProxy?: boolean; - parent?: RNode | null; - key?: KEY | null; equals?: (a: any, b: any) => boolean; + label?: string | null; } -export interface Root { - $?: T; -} - -export type KEY = string | symbol; function defaultEquality(a: any, b: any) { return a === b; @@ -61,17 +58,26 @@ export class RNode implements Signal { cleanups: ((oldValue: T) => void)[] = []; equals = defaultEquality; + label: string | null; constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) { this.isEffect = options?.isEffect || false; - + this.label = options?.label || null; if (typeof fnOrValue === 'function') { this.fn = fnOrValue as () => T; this._value = undefined as any; + // mark as dirty, queue to run later this.state = Dirty; if (this.isEffect) { - Effects.push(this); + if (options?.lazy !== false) { + Effects.push(this); + schedule(this); + } else { + this.update(); + // effect runs immediately, so state is now Fresh + this.state = Fresh; + } } } else { this.fn = undefined; @@ -80,14 +86,6 @@ export class RNode implements Signal { } } - get value(): T { - return this.get(); - } - - set value(v: T) { - this.set(v); - } - get(): T { this.track(); @@ -281,6 +279,13 @@ export class RNode implements Signal { setValue(value: any) { this._value = value; } + + dispose() { + this.fn = undefined; + this.observers = null; + this.sources = null; + this.cleanups = []; + } } export function onCleanup(fn: (oldValue: T) => void): void { @@ -300,16 +305,17 @@ export function runEffects(): void { } // 不进行响应式数据的使用追踪 -export function untrack(fn) { +export function untrack(fn: () => void) { if (runningRNode === null) { return fn(); } const preRContext = runningRNode; - runningRNode = null; + runningRNode = undefined; try { return fn(); } finally { runningRNode = preRContext; } } + diff --git a/packages/inula-reactive/src/RNodeCreator.ts b/packages/inula-reactive/src/RNodeCreator.ts index 580a71ff..9e4ef472 100644 --- a/packages/inula-reactive/src/RNodeCreator.ts +++ b/packages/inula-reactive/src/RNodeCreator.ts @@ -24,13 +24,11 @@ export function createReactive(raw?: T): Signal; export function createReactive(raw?: T): Signal; export function createReactive(raw?: T): Signal; export function createReactive | Array | symbol>(raw?: T): DeepReactive; -export function createReactive(raw?: T): DeepReactive | Signal { +export function createReactive(raw: T): DeepReactive | Signal { if (isPrimitive(raw) || raw === null || raw === undefined) { return new RNode(raw, { isSignal: true }); } else { - const node = new RProxyNode(null, { - root: { $: raw }, - }); + const node = new RProxyNode(raw); return node.proxy; } } @@ -43,9 +41,10 @@ export function createComputed(fn: T) { export function createWatch(fn: T) { const rNode = new RNode(fn, { isEffect: true, + lazy: false }); - rNode.get(); + return rNode; } export function getOrCreateChildProxy(value: unknown, parent: RProxyNode, key: string | symbol) { @@ -68,7 +67,7 @@ export function getOrCreateChildRNode(node: RProxyNode, key: string | symbo () => { node.get(); - return getRNodeVal(node)[key]; + return getRNodeVal(node)?.[key]; }, { isComputed: true, diff --git a/packages/inula-reactive/src/RProxyNode.ts b/packages/inula-reactive/src/RProxyNode.ts index 213db8e4..81ab38bb 100644 --- a/packages/inula-reactive/src/RProxyNode.ts +++ b/packages/inula-reactive/src/RProxyNode.ts @@ -15,16 +15,15 @@ import { createProxy } from './proxy/RProxyHandler'; import { setRNodeVal } from './RNodeAccessor'; -import { preciseCompare } from './comparison/InDepthComparison'; -import { isObject } from './Utils'; -import { Dirty, RNode, Root, runEffects } from './RNode'; -import { Computation, Signal } from './Types'; +import { isFunction, isObject } from './Utils'; +import { Dirty, RNode, runEffects } from './RNode'; +import { Computation, Root, Signal } from './Types'; export type DeepReactive = T extends Record ? SignalProxy : T extends () => infer Return - ? ComputationProxy - : Signal; + ? ComputationProxy + : Signal; export type SignalProxy = { [Val in keyof T]: SignalProxy; @@ -32,11 +31,11 @@ export type SignalProxy = { export type ComputationProxy = T extends Record ? { - readonly [Val in keyof T]: ComputationProxy; - } & Computation + readonly [Val in keyof T]: ComputationProxy; +} & Computation : Computation; -export interface RNodeOptions { +export interface PProxyNodeOptions { root?: Root | null; isSignal?: boolean; isEffect?: boolean; @@ -49,20 +48,30 @@ export interface RNodeOptions { export type KEY = string | symbol; -export class RProxyNode extends RNode { +/** + * RProxyNode + * @description + * RProxyNode is a proxy of RNode, it's used to create a reactive object. + * It's agent between Proxy and RNode, the createReactive will return the proxy. + * When create a RProxyNode, it will create a signal as the root of the reactive object. + * When accessing the properties of the proxy, the RProxyNode will be created as the derived child of the root signal. + * And every layer of the RProxyNode will be created as the derived child of the parent RProxyNode. + * When the value of root signal is changed, the children of the root signal will mark as dirty and re-run the effect queue. + */ +export class RProxyNode extends RNode { // 维护数据结构 root: Root | null; parent: RProxyNode | null = null; key: KEY | null; children: Map | null = null; - proxy: DeepReactive = null; + proxy: DeepReactive; extend: any; // 用于扩展,放一些自定义属性 isComputed = false; - constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) { + constructor(fnOrValue: (() => T) | T, options?: PProxyNodeOptions) { super(fnOrValue, options); this.isComputed = options?.isComputed || false; // Proxy type should be optimized @@ -77,6 +86,10 @@ export class RProxyNode extends RNode { } this.parent.children.set(this.key, this); } + + if (!isFunction(fnOrValue)) { + this.root = {$: fnOrValue}; + } } compare(prevValue: any, value: any) { @@ -100,10 +113,6 @@ export class RProxyNode extends RNode { } setByArrayModified(value: T) { - const prevValue = this.getValue(); - - preciseCompare(this, value, prevValue, true); - this.setDirty(); this.setValue(value); @@ -113,6 +122,9 @@ export class RProxyNode extends RNode { } getValue() { + if (!this.fn) { + return this.root?.$; + } return this._value; } diff --git a/packages/inula-reactive/src/SetScheduler.ts b/packages/inula-reactive/src/SetScheduler.ts new file mode 100644 index 00000000..d5d84fd2 --- /dev/null +++ b/packages/inula-reactive/src/SetScheduler.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula 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 { RNode, runEffects } from './RNode'; + +export let schedule: ((node: RNode) => void) = nextTick; + +export function setScheduler(fn = nextTick) { + schedule = fn; +} + +let effectsScheduled = false; + +/** queue () to run at the next idle time */ +function nextTick(): void { + if (!effectsScheduled) { + effectsScheduled = true; + + queueMicrotask(() => { + effectsScheduled = false; + runEffects(); + }); + } +} diff --git a/packages/inula-reactive/src/Types.ts b/packages/inula-reactive/src/Types.ts index dd448a35..2e14d3bb 100644 --- a/packages/inula-reactive/src/Types.ts +++ b/packages/inula-reactive/src/Types.ts @@ -18,81 +18,16 @@ export enum ArrayState { NotFresh = 1, } -export type PrimitiveType = string | number | boolean; - -export type ValueType = { [K in keyof T]: any } | Record | PrimitiveType; - -export interface BaseNodeFns { - /** - * 返回响应式对象的值,自动追踪依赖 - */ - get(): T; +export interface Root { + $?: T; /** - * 返回响应式对象的值,不追踪依赖 + * computed使用 + * @param {readOnly} 标识computed 是否处于写入状态 */ - read(): T; + readOnly?: boolean; } -export interface ProxyRNodeFn extends BaseNodeFns { - set>(value: V | ((prev: T) => V)); -} - -export interface AtomNodeFn extends BaseNodeFns { - set(value: V | ((prev: T) => V)); -} - -type PropsRecursive = T[K] extends PrimitiveType - ? AtomNode - : T[K] extends any[] - ? any[] & ProxyRNodeFn - : T extends Record - ? RecurseType - : T[K]; - -export type ProxyRNodeProps = { - [K in keyof T]: PropsRecursive>; -}; - -export type ComputedProps = { - [K in keyof T]: PropsRecursive>; -}; - -export type ProxyRNode = ProxyRNodeFn & ProxyRNodeProps; - -export type AtomNode = AtomNodeFn; - -export type Computed = BaseNodeFns & ComputedProps; - -export type ReactiveProxy = T extends PrimitiveType ? AtomNode : ProxyRNode; - -export interface BaseRNode { - // 标识Node类型 atomSymbol,nodeSymbol,computedSymbol - type: symbol; - root: Root; - children?: Map | Atom>; - usedRContexts?: RContextSet; - proxy?: any; - - diffOperator?: DiffOperator; - diffOperators?: DiffOperator[]; - states?: ArrayState[]; -} - -export interface RootRNode extends BaseRNode { - parentKey: null; - parent: null; -} - -export interface ChildrenRNode extends BaseRNode { - parentKey: string | symbol; - parent: RNode; -} - -export type RNode = RootRNode | ChildrenRNode; - -export type Reactive = RNode | Atom; - export type Signal = { /** * 返回响应式对象的值,自动追踪依赖 @@ -102,7 +37,7 @@ export type Signal = { * 返回响应式对象的值,不追踪依赖 */ read(): T; - set(value: T): void; + set(fnOrValue: T | ((prev: T) => T)): void; }; export type Computation = { diff --git a/packages/inula-reactive/src/Utils.ts b/packages/inula-reactive/src/Utils.ts index ff09dec5..2d720a05 100644 --- a/packages/inula-reactive/src/Utils.ts +++ b/packages/inula-reactive/src/Utils.ts @@ -17,7 +17,7 @@ import { RNode } from './RNode'; import { RProxyNode } from './RProxyNode'; import { Fn } from './Types'; -export function isReactiveObj(obj: any) { +export function isReactiveObj(obj: any): obj is RNode | RProxyNode { return obj instanceof RNode || obj instanceof RProxyNode; } diff --git a/packages/inula-reactive/src/comparison/ArrayDiff.ts b/packages/inula-reactive/src/comparison/ArrayDiff.ts deleted file mode 100644 index 3bc09f56..00000000 --- a/packages/inula-reactive/src/comparison/ArrayDiff.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula 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. - */ - -export enum Operation { - // 数组长度不同 - Nop = 0, - Insert = 1, - Delete = 2, -} - -export interface Diff { - action: Operation; - index: number; -} - -export interface DiffOperator { - isOnlyNop: boolean; - opts: Diff[]; -} - -function longestCommonPrefix(arr1: T[], arr2: T[]): number { - if (!arr1.length || !arr2.length || arr1[0] !== arr1[0]) { - return 0; - } - - let low = 0; - let start = 0; - // 最短数组的长度 - let high = Math.min(arr1.length, arr2.length); - while (low <= high) { - const mid = (high + low) >> 1; - if (isArrayEqual(arr1, arr2, start, mid)) { - low = mid + 1; - start = mid; - } else { - high = mid - 1; - } - } - return low - 1; -} - -function isArrayEqual(str1: T[], str2: T[], start: number, end: number): boolean { - for (let j = start; j < end; j++) { - if (str1[j] !== str2[j]) { - return false; - } - } - return true; -} - -/** - * @param origin 原始数组 - * @param target 目标数组 - * @returns 返回一个 Diff 数组,表示从原始数组转换为目标数组需要进行的操作 - */ -export function diffArray(origin: T[], target: T[]): DiffOperator { - // 使用二分查找计算共同前缀与后缀 - const prefixLen = longestCommonPrefix(origin, target); - const suffixLen = longestCommonPrefix([...origin].reverse(), [...target].reverse()); - // 删除原数组与目标数组的共同前缀与后缀 - const optimizedOrigin = origin.slice(prefixLen, origin.length - suffixLen); - const optimizedTarget = target.slice(prefixLen, target.length - suffixLen); - - const originLen = optimizedOrigin.length; - const targetLen = optimizedTarget.length; - - const dp: number[][] = Array.from(Array(originLen + 1), () => { - return Array(targetLen + 1).fill(0); - }); - const pathMatrix: Operation[][] = Array.from(Array(originLen + 1), () => { - return Array(targetLen + 1).fill(''); - }); - - let diffs: Diff[] = []; - - // 计算最长公共子序列 - for (let i = 1; i < originLen + 1; i++) { - for (let j = 1; j < targetLen + 1; j++) { - if (optimizedOrigin[i - 1] === optimizedTarget[j - 1]) { - dp[i][j] = dp[i - 1][j - 1] + 1; - // 如果相等,则表示不需要进行任何操作 - pathMatrix[i][j] = Operation.Nop; - } else if (dp[i - 1][j] > dp[i][j - 1]) { - dp[i][j] = dp[i - 1][j]; - // 如果不相等,则需要进行删除操作 - pathMatrix[i][j] = Operation.Delete; - } else { - dp[i][j] = dp[i][j - 1]; - // 如果不相等,则需要进行插入操作 - pathMatrix[i][j] = Operation.Insert; - } - } - } - - let hasDelete = false; - let hasInsert = false; - - // 计算操作序列 - function diff(oLen: number, tLen: number) { - const stack: Record[] = [{ i: oLen, j: tLen }]; - - while (stack.length > 0) { - const obj = stack.pop(); - const { i, j } = obj!; - if (i === 0 || j === 0) { - if (i !== 0) { - diffs.unshift( - ...optimizedOrigin.slice(0, i).map((item, idx) => ({ - action: Operation.Delete, - index: idx, - })) - ); - hasDelete = true; - } - if (j !== 0) { - diffs.unshift( - ...optimizedTarget.slice(0, j).map((item, idx) => ({ - action: Operation.Insert, - index: idx, - })) - ); - hasInsert = true; - } - } - - if (pathMatrix[i][j] === Operation.Nop) { - stack.push({ i: i - 1, j: j - 1 }); - // 如果不需要进行任何操作,则表示是公共元素,将其添加到 diffs 中 - diffs.unshift({ action: Operation.Nop, index: i - 1 }); - } else if (pathMatrix[i][j] === Operation.Delete) { - stack.push({ i: i - 1, j }); - // 如果需要进行删除操作,则将其添加到 diffs 中 - diffs.unshift({ action: Operation.Delete, index: i - 1 }); - hasDelete = true; - } else if (pathMatrix[i][j] === Operation.Insert) { - stack.push({ i, j: j - 1 }); - // 如果需要进行插入操作,则将其添加到 diffs 中 - diffs.unshift({ action: Operation.Insert, index: j - 1 }); - hasInsert = true; - } - } - } - - // 计算操作序列 - diff(originLen, targetLen); - - diffs.map(i => (i.index += prefixLen)); - - const prefixOpts = Array.from(Array(prefixLen), (_, index) => index).map(idx => ({ - action: Operation.Nop, - index: idx, - })); - - const suffixOpts = Array.from(Array(suffixLen), (_, index) => index).map(idx => ({ - action: Operation.Nop, - index: origin.length - suffixLen + idx, - })); - - diffs = prefixOpts.concat(diffs, suffixOpts); - - return { - isOnlyNop: !hasDelete && !hasInsert, - opts: diffs, - }; -} diff --git a/packages/inula-reactive/src/comparison/InDepthComparison.ts b/packages/inula-reactive/src/comparison/InDepthComparison.ts deleted file mode 100644 index 130a796f..00000000 --- a/packages/inula-reactive/src/comparison/InDepthComparison.ts +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2024 Huawei Technologies Co.,Ltd. - * - * openInula 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 {isArray, isObject} from '../Utils'; -import { diffArray, DiffOperator, Operation } from './ArrayDiff'; -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: RProxyNode, value: any, prevValue: any, isFromArrModify?: boolean) { - preciseCompareChildren(rNode, value, prevValue, isFromArrModify); - - // 触发父数据的RContext,不希望触发组件刷新(只触发computed和watch) - // TODO 暂时删除 - // triggerParents(reactive.parent); -} - -// 当value和prevValue都是对象或数组时,才触发 -function preciseCompareChildren(rNode: RProxyNode, value: any, prevValue: any, isFromArrModify?: boolean): boolean { - // 可以精准更新 - let canPreciseUpdate = true; - - const isArr = isArray(value); - const isPrevArr = isArray(prevValue); - - // 1、变化来自数组的Modify方法(某些行可能完全不变) - if (isFromArrModify) { - // 获取数组间差异,operator只能增删不能修改,修改会导致Effect不会随数据的位置变化 - const diffOperator = diffArray(prevValue, value); - const states: ArrayState[] = []; - - let childIndex = 0; - - for (const opt of diffOperator.opts) { - const idx = String(opt.index); - switch (opt.action) { - // 从已有RNode中取值 - case Operation.Nop: { - const childRNode = rNode.children?.get(idx); - - // children没有使用时,可以为undefined或没有该child - if (childRNode !== undefined) { - childRNode.key = String(childIndex); - states.push(ArrayState.Fresh); - childIndex++; - - // 删除旧的,重设新值。处理场景:元素还在,但是在数组中的位置变化了。 - rNode.children?.delete(String(opt.index)); - rNode.children?.set(childRNode.key, childRNode); - } - break; - } - // 从Value中新建RNode - case Operation.Insert: { - getOrCreateChildRNode(rNode, idx); - states.push(ArrayState.NotFresh); - childIndex++; - break; - } - case Operation.Delete: { - rNode.children?.delete(idx); - break; - } - } - } - - const diffOperators = getExtendProp(rNode, 'diffOperators', []); - - diffOperators.push(diffOperator); - - // 记录:新数据,哪些需要处理,哪些不需要 - setExtendProp(rNode, 'states', states); - - // 数组长度不同,确定会产生变化,调用callDependents一次 - // callRContexts(rNode); - rNode.setDirty(); - - return canPreciseUpdate; - } - - // 2、都是数组 - if (isArr && isPrevArr) { - const minLen = Math.min(value.length, prevValue.length); - - // 遍历数组或对象,触发子数据的Effects - const canPreciseUpdates = updateSameLengthArray(rNode, value, prevValue, minLen); - - const maxLen = Math.max(value.length, prevValue.length); - if (maxLen !== minLen || canPreciseUpdates.includes(false)) { - canPreciseUpdate = false; - } - - // 在reactive中保存opts - const diffOperator: DiffOperator = { - isOnlyNop: false, - opts: [], - }; - const states: ArrayState[] = []; - - // 相同长度的部分 - for (let i = 0; i < minLen; i++) { - diffOperator.opts.push({ action: Operation.Nop, index: i }); - // 如果该行数据无法精准更新,设置为NotFresh - states.push(canPreciseUpdates[i] ? ArrayState.Fresh : ArrayState.NotFresh); - } - - // 超出部分:新增 - if (value.length > prevValue.length) { - for (let i = minLen; i < maxLen; i++) { - diffOperator.opts.push({ action: Operation.Insert, index: i }); - states.push(ArrayState.NotFresh); - getOrCreateChildRNode(rNode, String(i)); - } - } else if (value.length < prevValue.length) { - // 减少部分:删除 - for (let i = minLen; i < maxLen; i++) { - diffOperator.opts.push({ action: Operation.Delete, index: i }); - states.push(ArrayState.NotFresh); - } - } - - diffOperator.isOnlyNop = !states.includes(ArrayState.NotFresh); - rNode.diffOperator = diffOperator; - rNode.states = states; - - return canPreciseUpdate; - } - - // 都是对象 - if (!isArr && !isPrevArr) { - const keys = Object.keys(value); - const prevKeys = Object.keys(prevValue); - - // 合并keys和prevKeys - const keySet = new Set(keys.concat(prevKeys)); - - keySet.forEach(key => { - const val = value[key]; - const prevVal = prevValue[key]; - const isChanged = val !== prevVal; - - // 如果数据有变化,就触发Effects - if (isChanged) { - const childRNode = rNode.children?.get(key); - - const isObj = isObject(val); - const isPrevObj = isObject(prevVal); - // val和prevVal都是对象或数组 - if (isObj) { - // 1、如果上一个属性无法精准更新,就不再递归下一个属性了 - // 2、如果childRNode为空,说明这个数据未被引用过,也不需要调用RContexts - if (canPreciseUpdate && childRNode !== undefined) { - canPreciseUpdate = preciseCompareChildren(childRNode as RNode, val, prevVal); - } - } else if (!isObj && !isPrevObj) { - // val和prevVal都不是对象或数组 - canPreciseUpdate = true; - } else { - // 类型不同(一个是对象或数组,另外一个不是) - canPreciseUpdate = false; - } - - // 有childRNode,说明这个数据被使引用过 - if (childRNode) { - childRNode.setDirty(); - } - } - }); - - return canPreciseUpdate; - } - - // 一个是对象,一个是数组 - canPreciseUpdate = false; - - return canPreciseUpdate; -} - -// 对于数组的变更,尽量尝试精准更新,会记录每行数据是否能够精准更新 -function updateSameLengthArray(rNode: RNode, value: any, prevValue: any, len: number): boolean[] { - const canPreciseUpdates: boolean[] = []; - - // 遍历数组或对象,触发子数据的RContexts - for (let i = 0; i < len; i++) { - const val = value[i]; - const prevVal = prevValue[i]; - const isChanged = val !== prevVal; - - // 如果数据有变化,就触发RContexts - if (isChanged) { - const childRNode = rNode.children?.get(String(i)); - - const isObj = isObject(val); - const isPrevObj = isObject(prevVal); - // val和prevVal都是对象或数组时 - if (isObj && isPrevObj) { - // 如果childRNode为空,说明这个数据未被引用过,也不需要调用RContexts - if (childRNode !== undefined) { - canPreciseUpdates[i] = preciseCompareChildren(childRNode, val, prevVal); - } - } else if (!isObj && !isPrevObj) { - // val和prevVal都不是对象或数组 - canPreciseUpdates[i] = true; - } else { - // 类型不同(一个是对象或数组,另外一个不是) - canPreciseUpdates[i] = false; - } - - // 有childRNode,说明这个数据被引用过 - if (childRNode) { - childRNode.setDirty(); - } - } else { - canPreciseUpdates[i] = true; - } - } - - return canPreciseUpdates; -} diff --git a/packages/inula-reactive/src/index.ts b/packages/inula-reactive/src/index.ts index 03dbca6f..f7c0588d 100644 --- a/packages/inula-reactive/src/index.ts +++ b/packages/inula-reactive/src/index.ts @@ -15,7 +15,8 @@ import { createComputed as computed, createReactive as reactive, createWatch as watch} from './RNodeCreator'; import { isReactiveObj } from './Utils'; -import { RNode, untrack } from './RNode'; +import { RNode, untrack, runEffects } from './RNode'; +import { setScheduler } from './SetScheduler'; export interface Index { reactive(initialValue: T): RNode; @@ -24,6 +25,9 @@ export interface Index { computed(fn: () => T): RNode; } +function unwrap(val: T) { + return isReactiveObj(val) ? val.read() : val; +} export { reactive, @@ -31,5 +35,8 @@ export { computed, isReactiveObj, RNode, - untrack + untrack, + unwrap, + setScheduler, + runEffects }; diff --git a/packages/inula-reactive/src/proxy/RProxyHandler.ts b/packages/inula-reactive/src/proxy/RProxyHandler.ts index 1d2f6cf7..1c329fd3 100644 --- a/packages/inula-reactive/src/proxy/RProxyHandler.ts +++ b/packages/inula-reactive/src/proxy/RProxyHandler.ts @@ -51,18 +51,17 @@ export function createProxy(proxyNode: T): DeepReactive }); } -const FNS: Record any> = { +const FNS = { [GET]: getFn, [READ]: readFn, [DELETE]: deleteFn, [ONCHANGE]: onChangeFn, -}; +} as const; function get(rNode: RProxyNode, key: string | symbol): any { // 处理 get, read, delete, onchange 方法 - const fn = FNS[key]; - if (fn) { - return () => fn(rNode); + if (key === GET || key === READ || key === DELETE || key === ONCHANGE) { + return () => FNS[key](rNode); } // 调用set()方法 @@ -88,7 +87,7 @@ function get(rNode: RProxyNode, key: string | symbol): any { if (isArray(rawObj) && key === 'length') { // 标记依赖 - // trackReactiveData(rNode); + rNode.track(); return value; } @@ -114,7 +113,7 @@ function get(rNode: RProxyNode, key: string | symbol): any { return function (callBackFn: any, thisArg?: any) { function cb(_: any, index: number, array: any[]) { const idx = String(index); - const itemProxy = getOrCreateChildProxy(array[idx], rNode, idx); + const itemProxy = getOrCreateChildProxy(array[index], rNode, idx); return callBackFn(itemProxy, index, array); } diff --git a/packages/inula-reactive/tsup.config.js b/packages/inula-reactive/tsup.config.js index 8b4416c0..98e54e82 100644 --- a/packages/inula-reactive/tsup.config.js +++ b/packages/inula-reactive/tsup.config.js @@ -17,4 +17,5 @@ export default defineConfig({ ], treeshake: true, clean: true, + dts: true, });