Match-id-a610f3bb353c9901f46e8f676e3d6b2092a862b6

This commit is contained in:
* 2022-11-18 22:13:06 +08:00
parent ef3e42adbe
commit f37a70552a
10 changed files with 221 additions and 53 deletions

View File

@ -19,19 +19,31 @@ export function isObject(obj: any): boolean {
}
export function isSet(obj: any): boolean {
return (obj !== null || obj !== undefined) && (Object.prototype.toString.call(obj) === '[object Set]' || obj.constructor === Set);
return (
(obj !== null || obj !== undefined) &&
(Object.prototype.toString.call(obj) === '[object Set]' || obj.constructor === Set)
);
}
export function isWeakSet(obj: any): boolean {
return (obj !== null || obj !== undefined) && (Object.prototype.toString.call(obj) === '[object WeakSet]' || obj.constructor === WeakSet);
return (
(obj !== null || obj !== undefined) &&
(Object.prototype.toString.call(obj) === '[object WeakSet]' || obj.constructor === WeakSet)
);
}
export function isMap(obj: any): boolean {
return (obj !== null || obj !== undefined) && (Object.prototype.toString.call(obj) === '[object Map]' || obj.constructor === Map);
return (
(obj !== null || obj !== undefined) &&
(Object.prototype.toString.call(obj) === '[object Map]' || obj.constructor === Map)
);
}
export function isWeakMap(obj: any): boolean {
return (obj !== null || obj !== undefined) && (Object.prototype.toString.call(obj) === '[object WeakMap]' || obj.constructor === WeakMap);
return (
(obj !== null || obj !== undefined) &&
(Object.prototype.toString.call(obj) === '[object WeakMap]' || obj.constructor === WeakMap)
);
}
export function isArray(obj: any): boolean {
@ -68,3 +80,74 @@ export function isSame(x, y) {
return Object.is(x, y);
}
}
export function getDetailedType(val: any) {
if (val === undefined) return 'undefined';
if (val === null) return 'null';
if (isCollection(val)) return 'collection';
if (isPromise(val)) return 'promise';
if (isArray(val)) return 'array';
if (isWeakMap(val)) return 'weakMap';
if (isMap(val)) return 'map';
if (isWeakSet(val)) return 'weakSet';
if (isSet(val)) return 'set';
return typeof val;
}
export function resolveMutation(from, to) {
if (getDetailedType(from) !== getDetailedType(to)) {
return { mutation: true, from, to };
}
switch (getDetailedType(from)) {
case 'array': {
let len = Math.max(from.length, to.length);
const res: any[] = [];
let found = false;
for (let i = 0; i < len; i++) {
if (from.length <= i) {
res[i] = { mutation: true, to: to[i] };
found = true;
} else if (to.length <= i) {
res[i] = { mutation: true, from: from[i] };
found = true;
} else {
res[i] = resolveMutation(from[i], to[i]);
if (res[i].mutation) found = true;
}
}
// TODO: resolve shifts
return { mutation: found, items: res, from, to };
}
case 'object': {
let keys = Object.keys({ ...from, ...to });
const res = {};
let found = false;
keys.forEach(key => {
if (!(key in from)) {
res[key] = { mutation: true, to: to[key] };
found = true;
return;
}
if (!(key in to)) {
res[key] = { mutation: true, from: from[key] };
found = true;
return;
}
res[key] = resolveMutation(from[key], to[key]);
if (res[key].mutation) found = true;
});
return { mutation: found, attributes: res, from, to };
}
// TODO: implement collections
default: {
if (from === to) return { mutation: false };
return { mutation: true, from, to };
}
}
}

View File

@ -41,6 +41,7 @@ function makeProxySnapshot(obj) {
export const devtools = {
emit: (type, data) => {
if (!window['__HORIZON_DEV_HOOK__']) return;
console.log('store snapshot:', makeStoreSnapshot({ type, data }));
window.postMessage({
type: 'HORIZON_DEV_TOOLS',

View File

@ -19,28 +19,28 @@ import type { IObserver } from './Observer';
* Observer
*/
export class HooklessObserver implements IObserver {
listeners: (() => void)[] = [];
listeners: ((mutation) => void)[] = [];
useProp(key: string | symbol): void {}
addListener(listener: () => void) {
addListener(listener: (mutation) => void) {
this.listeners.push(listener);
}
removeListener(listener: () => void) {
removeListener(listener: (mutation) => void) {
this.listeners = this.listeners.filter(item => item != listener);
}
setProp(key: string | symbol): void {
this.triggerChangeListeners();
setProp(key: string | symbol, mutation: any): void {
this.triggerChangeListeners(mutation);
}
triggerChangeListeners(): void {
triggerChangeListeners(mutation: any): void {
this.listeners.forEach(listener => {
if (!listener) {
return;
}
listener();
listener(mutation);
});
}

View File

@ -24,9 +24,9 @@ export interface IObserver {
removeListener: (listener: () => void) => void;
setProp: (key: string) => void;
setProp: (key: string, mutation: any) => void;
triggerChangeListeners: () => void;
triggerChangeListeners: (mutation: any) => void;
triggerUpdate: (vNode: any) => void;
@ -43,9 +43,9 @@ export class Observer implements IObserver {
keyVNodes = new Map();
listeners: (() => void)[] = [];
listeners: ((mutation) => void)[] = [];
watchers = {} as { [key: string]: ((key: string, oldValue: any, newValue: any) => void)[] };
watchers = {} as { [key: string]: ((key: string, oldValue: any, newValue: any, mutation: any) => void)[] };
// 对象的属性被使用时调用
useProp(key: string | symbol): void {
@ -76,7 +76,7 @@ export class Observer implements IObserver {
}
// 对象的属性被赋值时调用
setProp(key: string | symbol): void {
setProp(key: string | symbol, mutation: any): void {
const vNodes = this.keyVNodes.get(key);
vNodes?.forEach((vNode: VNode) => {
if (vNode.isStoreChange) {
@ -89,7 +89,7 @@ export class Observer implements IObserver {
this.triggerUpdate(vNode);
});
this.triggerChangeListeners();
this.triggerChangeListeners(mutation);
}
triggerUpdate(vNode: VNode): void {
@ -101,16 +101,16 @@ export class Observer implements IObserver {
launchUpdateFromVNode(vNode);
}
addListener(listener: () => void): void {
addListener(listener: (mutation) => void): void {
this.listeners.push(listener);
}
removeListener(listener: () => void): void {
removeListener(listener: (mutation) => void): void {
this.listeners = this.listeners.filter(item => item != listener);
}
triggerChangeListeners(): void {
this.listeners.forEach(listener => listener());
triggerChangeListeners(mutation: any): void {
this.listeners.forEach(listener => listener(mutation));
}
// 触发所有使用的props的VNode更新
@ -118,7 +118,7 @@ export class Observer implements IObserver {
const keyIt = this.keyVNodes.keys();
let keyItem = keyIt.next();
while (!keyItem.done) {
this.setProp(keyItem.value);
this.setProp(keyItem.value, {});
keyItem = keyIt.next();
}
}

View File

@ -16,6 +16,7 @@
import { getObserver } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { get as objectGet } from './ObjectProxyHandler';
import { resolveMutation } from '../../CommonUtils';
export function createArrayProxy(rawObj: any[]): any[] {
const handle = {
@ -53,6 +54,8 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldLength = rawObj.length;
const newValue = value;
const oldArray = JSON.parse(JSON.stringify(rawObj));
const ret = Reflect.set(rawObj, key, newValue, receiver);
const newLength = rawObj.length;
@ -62,17 +65,17 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
// 值不一样,触发监听器
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue);
cb(key, oldValue, newValue, resolveMutation(oldArray, rawObj));
});
}
// 触发属性变化
observer.setProp(key);
observer.setProp(key, resolveMutation(oldValue, rawObj));
}
if (oldLength !== newLength) {
// 触发数组的大小变化
observer.setProp('length');
observer.setProp('length', resolveMutation(oldValue, rawObj));
}
return ret;

View File

@ -15,6 +15,7 @@
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isMap, isWeakMap, isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
const COLLECTION_CHANGE = '_collectionChange';
const handler = {
@ -91,17 +92,17 @@ function set(
const observer = getObserver(rawObj);
if (valChange || !rawObj.has(key)) {
observer.setProp(COLLECTION_CHANGE);
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldValue, rawObj));
}
if (valChange) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue);
cb(key, oldValue, newValue, resolveMutation(oldValue, rawObj));
});
}
observer.setProp(key);
observer.setProp(key, resolveMutation(oldValue, rawObj));
}
return rawObj;
@ -109,12 +110,13 @@ function set(
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = JSON.parse(JSON.stringify(rawObj));
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
observer.setProp(value);
observer.setProp(COLLECTION_CHANGE);
observer.setProp(value, resolveMutation(oldCollection, rawObj));
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldCollection, rawObj));
}
return rawObj;
@ -138,12 +140,13 @@ function clear(rawObj: { size: number; clear: () => void }) {
}
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = JSON.parse(JSON.stringify(rawObj));
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
observer.setProp(key);
observer.setProp(COLLECTION_CHANGE);
observer.setProp(key, resolveMutation(oldCollection, rawObj));
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldCollection, rawObj));
return true;
}

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import { isSame } from '../../CommonUtils';
import { isSame, resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY } from '../../Constants';
@ -70,8 +70,7 @@ export function get(rawObj: object, key: string | symbol, receiver: any, singleL
}
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
console.log('ObjectProxyHandler.set()');
const oldObject = JSON.stringify(rawObj);
const oldObject = JSON.parse(JSON.stringify(rawObj));
const observer = getObserver(rawObj);
if (value && key == 'removeListener') {
@ -85,12 +84,10 @@ export function set(rawObj: object, key: string, value: any, receiver: any): boo
if (!isSame(newValue, oldValue)) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue);
cb(key, oldValue, newValue, resolveMutation(oldObject, rawObj));
});
}
observer.setProp(key);
observer.setProp(key, resolveMutation(oldObject, rawObj));
}
console.log('mutation from: ', JSON.parse(oldObject), ' to: ', ret);
return ret;
}

View File

@ -191,9 +191,10 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
store: storeObj,
});
storeObj.$subscribe(() => {
proxyObj.addListener(mutation => {
devtools.emit(STATE_CHANGE, {
store: storeObj,
mutation,
});
});

View File

@ -16,13 +16,13 @@
export interface IObserver {
useProp: (key: string | symbol) => void;
addListener: (listener: () => void) => void;
addListener: (listener: (mutation: any) => void) => void;
removeListener: (listener: () => void) => void;
removeListener: (listener: (mutation: any) => void) => void;
setProp: (key: string | symbol) => void;
setProp: (key: string | symbol, mutation: any) => void;
triggerChangeListeners: () => void;
triggerChangeListeners: (mutation: any) => void;
triggerUpdate: (vNode: any) => void;
@ -42,13 +42,13 @@ export type StoreConfig<S extends object, A extends UserActions<S>, C extends Us
};
export type UserActions<S extends object> = {
[K: string]: ActionFunction<S>
[K: string]: ActionFunction<S>;
};
type ActionFunction<S extends object> = (this: StoreObj<S, any, any>, state: S, ...args: any[]) => any;
export type StoreActions<S extends object, A extends UserActions<S>> = {
[K in keyof A]: Action<A[K], S>
[K in keyof A]: Action<A[K], S>;
};
type Action<T extends ActionFunction<any>, S extends object> = (
@ -61,8 +61,8 @@ export type StoreObj<S extends object, A extends UserActions<S>, C extends UserC
$a: StoreActions<S, A>;
$c: UserComputedValues<S>;
$queue: QueuedStoreActions<S, A>;
$subscribe: (listener: () => void) => void;
$unsubscribe: (listener: () => void) => void;
$subscribe: (listener: (mutation) => void) => void;
$unsubscribe: (listener: (mutation) => void) => void;
} & { [K in keyof S]: S[K] } & { [K in keyof A]: Action<A[K], S> } & { [K in keyof C]: ReturnType<C[K]> };
export type PlannedAction<S extends object, F extends ActionFunction<S>> = {
@ -74,11 +74,11 @@ export type PlannedAction<S extends object, F extends ActionFunction<S>> = {
type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
? []
: ((...b: T) => void) extends (a, ...b: infer I) => void
? I
: [];
? I
: [];
export type UserComputedValues<S extends object> = {
[K: string]: ComputedFunction<S>
[K: string]: ComputedFunction<S>;
};
type ComputedFunction<S extends object> = (state: S) => any;
@ -89,9 +89,9 @@ export type AsyncAction<T extends ActionFunction<any>, S extends object> = (
) => Promise<ReturnType<T>>;
export type QueuedStoreActions<S extends object, A extends UserActions<S>> = {
[K in keyof A]: AsyncAction<A[K], S>
[K in keyof A]: AsyncAction<A[K], S>;
};
export type ComputedValues<S extends object, C extends UserComputedValues<S>> = {
[K in keyof C]: ReturnType<C[K]>
[K in keyof C]: ReturnType<C[K]>;
};

View File

@ -0,0 +1,80 @@
import { resolveMutation } from '../../../../libs/horizon/src/horizonx/CommonUtils';
describe('Mutation resolve', () => {
it('should resolve mutation different types', () => {
const mutation = resolveMutation(null, 42);
expect(mutation.mutation).toBe(true);
expect(mutation.from).toBe(null);
expect(mutation.to).toBe(42);
});
it('should resolve mutation same type types, different values', () => {
const mutation = resolveMutation(13, 42);
expect(mutation.mutation).toBe(true);
expect(mutation.from).toBe(13);
expect(mutation.to).toBe(42);
});
it('should resolve mutation same type types, same values', () => {
const mutation = resolveMutation(42, 42);
expect(mutation.mutation).toBe(false);
expect(Object.keys(mutation).length).toBe(1);
});
it('should resolve mutation same type types, same objects', () => {
const mutation = resolveMutation({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, same array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]);
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, longer array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]);
expect(mutation.mutation).toBe(true);
expect(mutation.items[5].mutation).toBe(true);
expect(mutation.items[5].to).toBe(6);
});
it('should resolve mutation same type types, shorter array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4]);
expect(mutation.mutation).toBe(true);
expect(mutation.items[4].mutation).toBe(true);
expect(mutation.items[4].from).toBe(5);
});
it('should resolve mutation same type types, changed array', () => {
const mutation = resolveMutation([1, 2, 3, 4, 5], [1, 2, 3, 4, 'a']);
expect(mutation.mutation).toBe(true);
expect(mutation.items[4].mutation).toBe(true);
expect(mutation.items[4].from).toBe(5);
expect(mutation.items[4].to).toBe('a');
});
it('should resolve mutation same type types, same object', () => {
const mutation = resolveMutation({ a: 1, b: 2 }, { a: 1, b: 2 });
console.log(mutation);
expect(mutation.mutation).toBe(false);
});
it('should resolve mutation same type types, changed object', () => {
const mutation = resolveMutation({ a: 1, b: 2, c: 3 }, { a: 1, c: 2 });
expect(mutation.mutation).toBe(true);
expect(mutation.attributes.a.mutation).toBe(false);
expect(mutation.attributes.b.mutation).toBe(true);
expect(mutation.attributes.b.from).toBe(2);
expect(mutation.attributes.c.to).toBe(2);
});
});