!130 fix(reactive): fix type error

Merge pull request !130 from Hoikan/auto-1898932-reactive-2a495c8c
This commit is contained in:
陈超涛 2024-02-04 09:40:24 +00:00 committed by Gitee
commit bd3f1b9694
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
12 changed files with 120 additions and 534 deletions

View File

@ -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"
}
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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();
});
}
}

View File

@ -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> = {

View File

@ -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;
}

View File

@ -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,
};
}

View File

@ -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;
}

View File

@ -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
};

View File

@ -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);
}

View File

@ -17,4 +17,5 @@ export default defineConfig({
],
treeshake: true,
clean: true,
dts: true,
});