!130 fix(reactive): fix type error
Merge pull request !130 from Hoikan/auto-1898932-reactive-2a495c8c
This commit is contained in:
commit
bd3f1b9694
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import { printNode } from './utils/printNode';
|
||||
import { Signal } from './Types';
|
||||
import { isFunction } from './Utils';
|
||||
import { schedule } from './SetScheduler';
|
||||
|
||||
let runningRNode: RNode<any> | undefined = undefined; // 当前正执行的RNode
|
||||
let calledGets: RNode<any>[] | 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<any> | null;
|
||||
key?: KEY | null;
|
||||
equals?: (a: any, b: any) => boolean;
|
||||
label?: string | null;
|
||||
}
|
||||
|
||||
export interface Root<T> {
|
||||
$?: T;
|
||||
}
|
||||
|
||||
export type KEY = string | symbol;
|
||||
|
||||
function defaultEquality(a: any, b: any) {
|
||||
return a === b;
|
||||
|
@ -61,17 +58,26 @@ export class RNode<T = any> implements Signal<T> {
|
|||
|
||||
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<T = any> implements Signal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
set value(v: T) {
|
||||
this.set(v);
|
||||
}
|
||||
|
||||
get(): T {
|
||||
this.track();
|
||||
|
||||
|
@ -281,6 +279,13 @@ export class RNode<T = any> implements Signal<T> {
|
|||
setValue(value: any) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.fn = undefined;
|
||||
this.observers = null;
|
||||
this.sources = null;
|
||||
this.cleanups = [];
|
||||
}
|
||||
}
|
||||
|
||||
export function onCleanup<T = any>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,13 +24,11 @@ export function createReactive<T extends number>(raw?: T): Signal<number>;
|
|||
export function createReactive<T extends symbol>(raw?: T): Signal<symbol>;
|
||||
export function createReactive<T extends number | string | symbol>(raw?: T): Signal<T>;
|
||||
export function createReactive<T extends Record<any, any> | Array<any> | symbol>(raw?: T): DeepReactive<T>;
|
||||
export function createReactive<T extends NonFunctionType>(raw?: T): DeepReactive<T> | Signal<T> {
|
||||
export function createReactive<T extends NonFunctionType>(raw: T): DeepReactive<T> | Signal<T> {
|
||||
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
||||
return new RNode(raw, { isSignal: true });
|
||||
} else {
|
||||
const node = new RProxyNode<T>(null, {
|
||||
root: { $: raw },
|
||||
});
|
||||
const node = new RProxyNode(raw);
|
||||
return node.proxy;
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +41,10 @@ export function createComputed<T extends NoArgFn>(fn: T) {
|
|||
export function createWatch<T>(fn: T) {
|
||||
const rNode = new RNode(fn, {
|
||||
isEffect: true,
|
||||
lazy: false
|
||||
});
|
||||
|
||||
rNode.get();
|
||||
return rNode;
|
||||
}
|
||||
|
||||
export function getOrCreateChildProxy(value: unknown, parent: RProxyNode<any>, key: string | symbol) {
|
||||
|
@ -68,7 +67,7 @@ export function getOrCreateChildRNode(node: RProxyNode<any>, key: string | symbo
|
|||
() => {
|
||||
node.get();
|
||||
|
||||
return getRNodeVal(node)[key];
|
||||
return getRNodeVal(node)?.[key];
|
||||
},
|
||||
{
|
||||
isComputed: true,
|
||||
|
|
|
@ -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> = T extends Record<string, unknown>
|
||||
? SignalProxy<T>
|
||||
: T extends () => infer Return
|
||||
? ComputationProxy<Return>
|
||||
: Signal<T>;
|
||||
? ComputationProxy<Return>
|
||||
: Signal<T>;
|
||||
|
||||
export type SignalProxy<T> = {
|
||||
[Val in keyof T]: SignalProxy<T[Val]>;
|
||||
|
@ -32,11 +31,11 @@ export type SignalProxy<T> = {
|
|||
|
||||
export type ComputationProxy<T> = T extends Record<string, unknown>
|
||||
? {
|
||||
readonly [Val in keyof T]: ComputationProxy<T[Val]>;
|
||||
} & Computation<T>
|
||||
readonly [Val in keyof T]: ComputationProxy<T[Val]>;
|
||||
} & Computation<T>
|
||||
: Computation<T>;
|
||||
|
||||
export interface RNodeOptions {
|
||||
export interface PProxyNodeOptions {
|
||||
root?: Root<any> | null;
|
||||
isSignal?: boolean;
|
||||
isEffect?: boolean;
|
||||
|
@ -49,20 +48,30 @@ export interface RNodeOptions {
|
|||
|
||||
export type KEY = string | symbol;
|
||||
|
||||
export class RProxyNode<T = any> extends RNode<T> {
|
||||
/**
|
||||
* 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<T = any> extends RNode<unknown> {
|
||||
// 维护数据结构
|
||||
root: Root<T> | null;
|
||||
parent: RProxyNode | null = null;
|
||||
key: KEY | null;
|
||||
children: Map<KEY, RProxyNode> | null = null;
|
||||
|
||||
proxy: DeepReactive<T> = null;
|
||||
proxy: DeepReactive<T>;
|
||||
|
||||
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<T = any> extends RNode<T> {
|
|||
}
|
||||
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<T = any> extends RNode<T> {
|
|||
}
|
||||
|
||||
setByArrayModified(value: T) {
|
||||
const prevValue = this.getValue();
|
||||
|
||||
preciseCompare(this, value, prevValue, true);
|
||||
|
||||
this.setDirty();
|
||||
|
||||
this.setValue(value);
|
||||
|
@ -113,6 +122,9 @@ export class RProxyNode<T = any> extends RNode<T> {
|
|||
}
|
||||
|
||||
getValue() {
|
||||
if (!this.fn) {
|
||||
return this.root?.$;
|
||||
}
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<any>) => 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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -18,81 +18,16 @@ export enum ArrayState {
|
|||
NotFresh = 1,
|
||||
}
|
||||
|
||||
export type PrimitiveType = string | number | boolean;
|
||||
|
||||
export type ValueType<T> = { [K in keyof T]: any } | Record<string, any> | PrimitiveType;
|
||||
|
||||
export interface BaseNodeFns<T> {
|
||||
/**
|
||||
* 返回响应式对象的值,自动追踪依赖
|
||||
*/
|
||||
get(): T;
|
||||
export interface Root<T> {
|
||||
$?: T;
|
||||
|
||||
/**
|
||||
* 返回响应式对象的值,不追踪依赖
|
||||
* computed使用
|
||||
* @param {readOnly} 标识computed 是否处于写入状态
|
||||
*/
|
||||
read(): T;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface ProxyRNodeFn<T> extends BaseNodeFns<T> {
|
||||
set<V = ValueType<T>>(value: V | ((prev: T) => V));
|
||||
}
|
||||
|
||||
export interface AtomNodeFn<T> extends BaseNodeFns<T> {
|
||||
set<V extends PrimitiveType>(value: V | ((prev: T) => V));
|
||||
}
|
||||
|
||||
type PropsRecursive<T, K extends keyof T, RecurseType> = T[K] extends PrimitiveType
|
||||
? AtomNode<T[K]>
|
||||
: T[K] extends any[]
|
||||
? any[] & ProxyRNodeFn<T[K]>
|
||||
: T extends Record<string, any>
|
||||
? RecurseType
|
||||
: T[K];
|
||||
|
||||
export type ProxyRNodeProps<T> = {
|
||||
[K in keyof T]: PropsRecursive<T, K, ProxyRNode<T[K]>>;
|
||||
};
|
||||
|
||||
export type ComputedProps<T> = {
|
||||
[K in keyof T]: PropsRecursive<T, K, BaseNodeFns<T[K]>>;
|
||||
};
|
||||
|
||||
export type ProxyRNode<T> = ProxyRNodeFn<T> & ProxyRNodeProps<T>;
|
||||
|
||||
export type AtomNode<T> = AtomNodeFn<T>;
|
||||
|
||||
export type Computed<T> = BaseNodeFns<T> & ComputedProps<T>;
|
||||
|
||||
export type ReactiveProxy<T> = T extends PrimitiveType ? AtomNode<T> : ProxyRNode<T>;
|
||||
|
||||
export interface BaseRNode<T> {
|
||||
// 标识Node类型 atomSymbol,nodeSymbol,computedSymbol
|
||||
type: symbol;
|
||||
root: Root<T>;
|
||||
children?: Map<string | symbol, RNode<T> | Atom<T>>;
|
||||
usedRContexts?: RContextSet;
|
||||
proxy?: any;
|
||||
|
||||
diffOperator?: DiffOperator;
|
||||
diffOperators?: DiffOperator[];
|
||||
states?: ArrayState[];
|
||||
}
|
||||
|
||||
export interface RootRNode<T> extends BaseRNode<T> {
|
||||
parentKey: null;
|
||||
parent: null;
|
||||
}
|
||||
|
||||
export interface ChildrenRNode<T> extends BaseRNode<T> {
|
||||
parentKey: string | symbol;
|
||||
parent: RNode;
|
||||
}
|
||||
|
||||
export type RNode<T = any> = RootRNode<T> | ChildrenRNode<T>;
|
||||
|
||||
export type Reactive<T = any> = RNode<T> | Atom<T>;
|
||||
|
||||
export type Signal<T> = {
|
||||
/**
|
||||
* 返回响应式对象的值,自动追踪依赖
|
||||
|
@ -102,7 +37,7 @@ export type Signal<T> = {
|
|||
* 返回响应式对象的值,不追踪依赖
|
||||
*/
|
||||
read(): T;
|
||||
set(value: T): void;
|
||||
set(fnOrValue: T | ((prev: T) => T)): void;
|
||||
};
|
||||
|
||||
export type Computation<T> = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T>(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<T>(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<T>(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<string, number>[] = [{ 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,
|
||||
};
|
||||
}
|
|
@ -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<any>, 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;
|
||||
}
|
|
@ -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<T>(initialValue: T): RNode<T>;
|
||||
|
@ -24,6 +25,9 @@ export interface Index {
|
|||
|
||||
computed<T>(fn: () => T): RNode<T>;
|
||||
}
|
||||
function unwrap<T>(val: T) {
|
||||
return isReactiveObj(val) ? val.read() : val;
|
||||
}
|
||||
|
||||
export {
|
||||
reactive,
|
||||
|
@ -31,5 +35,8 @@ export {
|
|||
computed,
|
||||
isReactiveObj,
|
||||
RNode,
|
||||
untrack
|
||||
untrack,
|
||||
unwrap,
|
||||
setScheduler,
|
||||
runEffects
|
||||
};
|
||||
|
|
|
@ -51,18 +51,17 @@ export function createProxy<T extends RProxyNode>(proxyNode: T): DeepReactive<T>
|
|||
});
|
||||
}
|
||||
|
||||
const FNS: Record<typeof GET | typeof READ | typeof DELETE | typeof ONCHANGE, (args: RNode) => 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ export default defineConfig({
|
|||
],
|
||||
treeshake: true,
|
||||
clean: true,
|
||||
dts: true,
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue