Match-id-a610f3bb353c9901f46e8f676e3d6b2092a862b6
This commit is contained in:
parent
ef3e42adbe
commit
f37a70552a
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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]>;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue