fix(reactive): fix type error
This commit is contained in:
parent
72878d0d6b
commit
1cdaf1bdc8
|
@ -6,6 +6,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
"test": "jest --config=jest.config.js",
|
"test": "jest --config=jest.config.js",
|
||||||
"bench": "node --experimental-specifier-resolution=node --inspect ./bench/index.js"
|
"bench": "node --experimental-specifier-resolution=node --inspect ./bench/index.js"
|
||||||
},
|
},
|
||||||
|
@ -16,7 +17,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"cli-table": "^0.3.11",
|
"cli-table": "^0.3.11",
|
||||||
|
"esbuild-plugin-replace": "^1.4.0",
|
||||||
"tsup": "^6.7.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 { printNode } from './utils/printNode';
|
||||||
import { Signal } from './Types';
|
import { Signal } from './Types';
|
||||||
import { isFunction } from './Utils';
|
import { isFunction } from './Utils';
|
||||||
|
import { schedule } from './SetScheduler';
|
||||||
|
|
||||||
let runningRNode: RNode<any> | undefined = undefined; // 当前正执行的RNode
|
let runningRNode: RNode<any> | undefined = undefined; // 当前正执行的RNode
|
||||||
let calledGets: RNode<any>[] | null = null;
|
let calledGets: RNode<any>[] | null = null;
|
||||||
|
@ -32,18 +33,14 @@ type NonClean = typeof Check | typeof Dirty;
|
||||||
export interface RNodeOptions {
|
export interface RNodeOptions {
|
||||||
isSignal?: boolean;
|
isSignal?: boolean;
|
||||||
isEffect?: boolean;
|
isEffect?: boolean;
|
||||||
|
// effect is lazy by default
|
||||||
|
lazy?: boolean;
|
||||||
isComputed?: boolean;
|
isComputed?: boolean;
|
||||||
isProxy?: boolean;
|
isProxy?: boolean;
|
||||||
parent?: RNode<any> | null;
|
|
||||||
key?: KEY | null;
|
|
||||||
equals?: (a: any, b: any) => boolean;
|
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) {
|
function defaultEquality(a: any, b: any) {
|
||||||
return a === b;
|
return a === b;
|
||||||
|
@ -61,17 +58,26 @@ export class RNode<T = any> implements Signal<T> {
|
||||||
|
|
||||||
cleanups: ((oldValue: T) => void)[] = [];
|
cleanups: ((oldValue: T) => void)[] = [];
|
||||||
equals = defaultEquality;
|
equals = defaultEquality;
|
||||||
|
label: string | null;
|
||||||
|
|
||||||
constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) {
|
constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) {
|
||||||
this.isEffect = options?.isEffect || false;
|
this.isEffect = options?.isEffect || false;
|
||||||
|
this.label = options?.label || null;
|
||||||
if (typeof fnOrValue === 'function') {
|
if (typeof fnOrValue === 'function') {
|
||||||
this.fn = fnOrValue as () => T;
|
this.fn = fnOrValue as () => T;
|
||||||
this._value = undefined as any;
|
this._value = undefined as any;
|
||||||
|
// mark as dirty, queue to run later
|
||||||
this.state = Dirty;
|
this.state = Dirty;
|
||||||
|
|
||||||
if (this.isEffect) {
|
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 {
|
} else {
|
||||||
this.fn = undefined;
|
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 {
|
get(): T {
|
||||||
this.track();
|
this.track();
|
||||||
|
|
||||||
|
@ -281,6 +279,13 @@ export class RNode<T = any> implements Signal<T> {
|
||||||
setValue(value: any) {
|
setValue(value: any) {
|
||||||
this._value = value;
|
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 {
|
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) {
|
if (runningRNode === null) {
|
||||||
return fn();
|
return fn();
|
||||||
}
|
}
|
||||||
|
|
||||||
const preRContext = runningRNode;
|
const preRContext = runningRNode;
|
||||||
runningRNode = null;
|
runningRNode = undefined;
|
||||||
try {
|
try {
|
||||||
return fn();
|
return fn();
|
||||||
} finally {
|
} finally {
|
||||||
runningRNode = preRContext;
|
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 symbol>(raw?: T): Signal<symbol>;
|
||||||
export function createReactive<T extends number | string | symbol>(raw?: T): Signal<T>;
|
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 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) {
|
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
||||||
return new RNode(raw, { isSignal: true });
|
return new RNode(raw, { isSignal: true });
|
||||||
} else {
|
} else {
|
||||||
const node = new RProxyNode<T>(null, {
|
const node = new RProxyNode(raw);
|
||||||
root: { $: raw },
|
|
||||||
});
|
|
||||||
return node.proxy;
|
return node.proxy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,9 +41,10 @@ export function createComputed<T extends NoArgFn>(fn: T) {
|
||||||
export function createWatch<T>(fn: T) {
|
export function createWatch<T>(fn: T) {
|
||||||
const rNode = new RNode(fn, {
|
const rNode = new RNode(fn, {
|
||||||
isEffect: true,
|
isEffect: true,
|
||||||
|
lazy: false
|
||||||
});
|
});
|
||||||
|
|
||||||
rNode.get();
|
return rNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrCreateChildProxy(value: unknown, parent: RProxyNode<any>, key: string | symbol) {
|
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();
|
node.get();
|
||||||
|
|
||||||
return getRNodeVal(node)[key];
|
return getRNodeVal(node)?.[key];
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isComputed: true,
|
isComputed: true,
|
||||||
|
|
|
@ -15,16 +15,15 @@
|
||||||
|
|
||||||
import { createProxy } from './proxy/RProxyHandler';
|
import { createProxy } from './proxy/RProxyHandler';
|
||||||
import { setRNodeVal } from './RNodeAccessor';
|
import { setRNodeVal } from './RNodeAccessor';
|
||||||
import { preciseCompare } from './comparison/InDepthComparison';
|
import { isFunction, isObject } from './Utils';
|
||||||
import { isObject } from './Utils';
|
import { Dirty, RNode, runEffects } from './RNode';
|
||||||
import { Dirty, RNode, Root, runEffects } from './RNode';
|
import { Computation, Root, Signal } from './Types';
|
||||||
import { Computation, Signal } from './Types';
|
|
||||||
|
|
||||||
export type DeepReactive<T> = T extends Record<string, unknown>
|
export type DeepReactive<T> = T extends Record<string, unknown>
|
||||||
? SignalProxy<T>
|
? SignalProxy<T>
|
||||||
: T extends () => infer Return
|
: T extends () => infer Return
|
||||||
? ComputationProxy<Return>
|
? ComputationProxy<Return>
|
||||||
: Signal<T>;
|
: Signal<T>;
|
||||||
|
|
||||||
export type SignalProxy<T> = {
|
export type SignalProxy<T> = {
|
||||||
[Val in keyof T]: SignalProxy<T[Val]>;
|
[Val in keyof T]: SignalProxy<T[Val]>;
|
||||||
|
@ -32,11 +31,11 @@ export type SignalProxy<T> = {
|
||||||
|
|
||||||
export type ComputationProxy<T> = T extends Record<string, unknown>
|
export type ComputationProxy<T> = T extends Record<string, unknown>
|
||||||
? {
|
? {
|
||||||
readonly [Val in keyof T]: ComputationProxy<T[Val]>;
|
readonly [Val in keyof T]: ComputationProxy<T[Val]>;
|
||||||
} & Computation<T>
|
} & Computation<T>
|
||||||
: Computation<T>;
|
: Computation<T>;
|
||||||
|
|
||||||
export interface RNodeOptions {
|
export interface PProxyNodeOptions {
|
||||||
root?: Root<any> | null;
|
root?: Root<any> | null;
|
||||||
isSignal?: boolean;
|
isSignal?: boolean;
|
||||||
isEffect?: boolean;
|
isEffect?: boolean;
|
||||||
|
@ -49,20 +48,30 @@ export interface RNodeOptions {
|
||||||
|
|
||||||
export type KEY = string | symbol;
|
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;
|
root: Root<T> | null;
|
||||||
parent: RProxyNode | null = null;
|
parent: RProxyNode | null = null;
|
||||||
key: KEY | null;
|
key: KEY | null;
|
||||||
children: Map<KEY, RProxyNode> | null = null;
|
children: Map<KEY, RProxyNode> | null = null;
|
||||||
|
|
||||||
proxy: DeepReactive<T> = null;
|
proxy: DeepReactive<T>;
|
||||||
|
|
||||||
extend: any; // 用于扩展,放一些自定义属性
|
extend: any; // 用于扩展,放一些自定义属性
|
||||||
|
|
||||||
isComputed = false;
|
isComputed = false;
|
||||||
|
|
||||||
constructor(fnOrValue: (() => T) | T, options?: RNodeOptions) {
|
constructor(fnOrValue: (() => T) | T, options?: PProxyNodeOptions) {
|
||||||
super(fnOrValue, options);
|
super(fnOrValue, options);
|
||||||
this.isComputed = options?.isComputed || false;
|
this.isComputed = options?.isComputed || false;
|
||||||
// Proxy type should be optimized
|
// Proxy type should be optimized
|
||||||
|
@ -77,6 +86,10 @@ export class RProxyNode<T = any> extends RNode<T> {
|
||||||
}
|
}
|
||||||
this.parent.children.set(this.key, this);
|
this.parent.children.set(this.key, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isFunction(fnOrValue)) {
|
||||||
|
this.root = {$: fnOrValue};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compare(prevValue: any, value: any) {
|
compare(prevValue: any, value: any) {
|
||||||
|
@ -100,10 +113,6 @@ export class RProxyNode<T = any> extends RNode<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setByArrayModified(value: T) {
|
setByArrayModified(value: T) {
|
||||||
const prevValue = this.getValue();
|
|
||||||
|
|
||||||
preciseCompare(this, value, prevValue, true);
|
|
||||||
|
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
|
|
||||||
this.setValue(value);
|
this.setValue(value);
|
||||||
|
@ -113,6 +122,9 @@ export class RProxyNode<T = any> extends RNode<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue() {
|
getValue() {
|
||||||
|
if (!this.fn) {
|
||||||
|
return this.root?.$;
|
||||||
|
}
|
||||||
return this._value;
|
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,
|
NotFresh = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PrimitiveType = string | number | boolean;
|
export interface Root<T> {
|
||||||
|
$?: T;
|
||||||
export type ValueType<T> = { [K in keyof T]: any } | Record<string, any> | PrimitiveType;
|
|
||||||
|
|
||||||
export interface BaseNodeFns<T> {
|
|
||||||
/**
|
|
||||||
* 返回响应式对象的值,自动追踪依赖
|
|
||||||
*/
|
|
||||||
get(): 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> = {
|
export type Signal<T> = {
|
||||||
/**
|
/**
|
||||||
* 返回响应式对象的值,自动追踪依赖
|
* 返回响应式对象的值,自动追踪依赖
|
||||||
|
@ -102,7 +37,7 @@ export type Signal<T> = {
|
||||||
* 返回响应式对象的值,不追踪依赖
|
* 返回响应式对象的值,不追踪依赖
|
||||||
*/
|
*/
|
||||||
read(): T;
|
read(): T;
|
||||||
set(value: T): void;
|
set(fnOrValue: T | ((prev: T) => T)): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Computation<T> = {
|
export type Computation<T> = {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { RNode } from './RNode';
|
||||||
import { RProxyNode } from './RProxyNode';
|
import { RProxyNode } from './RProxyNode';
|
||||||
import { Fn } from './Types';
|
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;
|
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 { createComputed as computed, createReactive as reactive, createWatch as watch} from './RNodeCreator';
|
||||||
import { isReactiveObj } from './Utils';
|
import { isReactiveObj } from './Utils';
|
||||||
import { RNode, untrack } from './RNode';
|
import { RNode, untrack, runEffects } from './RNode';
|
||||||
|
import { setScheduler } from './SetScheduler';
|
||||||
|
|
||||||
export interface Index {
|
export interface Index {
|
||||||
reactive<T>(initialValue: T): RNode<T>;
|
reactive<T>(initialValue: T): RNode<T>;
|
||||||
|
@ -24,6 +25,9 @@ export interface Index {
|
||||||
|
|
||||||
computed<T>(fn: () => T): RNode<T>;
|
computed<T>(fn: () => T): RNode<T>;
|
||||||
}
|
}
|
||||||
|
function unwrap<T>(val: T) {
|
||||||
|
return isReactiveObj(val) ? val.read() : val;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
reactive,
|
reactive,
|
||||||
|
@ -31,5 +35,8 @@ export {
|
||||||
computed,
|
computed,
|
||||||
isReactiveObj,
|
isReactiveObj,
|
||||||
RNode,
|
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,
|
[GET]: getFn,
|
||||||
[READ]: readFn,
|
[READ]: readFn,
|
||||||
[DELETE]: deleteFn,
|
[DELETE]: deleteFn,
|
||||||
[ONCHANGE]: onChangeFn,
|
[ONCHANGE]: onChangeFn,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
function get(rNode: RProxyNode, key: string | symbol): any {
|
function get(rNode: RProxyNode, key: string | symbol): any {
|
||||||
// 处理 get, read, delete, onchange 方法
|
// 处理 get, read, delete, onchange 方法
|
||||||
const fn = FNS[key];
|
if (key === GET || key === READ || key === DELETE || key === ONCHANGE) {
|
||||||
if (fn) {
|
return () => FNS[key](rNode);
|
||||||
return () => fn(rNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用set()方法
|
// 调用set()方法
|
||||||
|
@ -88,7 +87,7 @@ function get(rNode: RProxyNode, key: string | symbol): any {
|
||||||
|
|
||||||
if (isArray(rawObj) && key === 'length') {
|
if (isArray(rawObj) && key === 'length') {
|
||||||
// 标记依赖
|
// 标记依赖
|
||||||
// trackReactiveData(rNode);
|
rNode.track();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ function get(rNode: RProxyNode, key: string | symbol): any {
|
||||||
return function (callBackFn: any, thisArg?: any) {
|
return function (callBackFn: any, thisArg?: any) {
|
||||||
function cb(_: any, index: number, array: any[]) {
|
function cb(_: any, index: number, array: any[]) {
|
||||||
const idx = String(index);
|
const idx = String(index);
|
||||||
const itemProxy = getOrCreateChildProxy(array[idx], rNode, idx);
|
const itemProxy = getOrCreateChildProxy(array[index], rNode, idx);
|
||||||
return callBackFn(itemProxy, index, array);
|
return callBackFn(itemProxy, index, array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,5 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
treeshake: true,
|
treeshake: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
dts: true,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue