Match-id-835607f8b2fd6715e190a54cee5627437d7cb51f
This commit is contained in:
parent
9ae4489880
commit
cc93ced478
|
@ -73,7 +73,7 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
reduxAdapter: true,
|
isReduxAdapter: true,
|
||||||
},
|
},
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { useState, useContext, useEffect, useRef } from '../../renderer/hooks/HookExternal';
|
import { useState, useContext, useEffect, useRef } from '../../renderer/hooks/HookExternal';
|
||||||
import { createContext } from '../../renderer/components/context/CreateContext';
|
import { createContext } from '../../renderer/components/context/CreateContext';
|
||||||
import { createElement } from '../../external/JSXElement';
|
import { createElement } from '../../external/JSXElement';
|
||||||
|
@ -78,31 +77,12 @@ export const useStore = () => {
|
||||||
return createStoreHook(DefaultContext)();
|
return createStoreHook(DefaultContext)();
|
||||||
};
|
};
|
||||||
|
|
||||||
// function shallowCompare(a,b){
|
|
||||||
// return Object.keys(a).length === Object.keys(b).length &&
|
|
||||||
// Object.keys(a).every(key => a[key] === b[key]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//TODO: implement options
|
|
||||||
// context?: Object,
|
|
||||||
// areStatesEqual?: Function, :)
|
|
||||||
// areOwnPropsEqual?: Function,
|
|
||||||
// areStatePropsEqual?: Function,
|
|
||||||
// areMergedPropsEqual?: Function,
|
|
||||||
// forwardRef?: boolean,
|
|
||||||
// const defaultOptions = {
|
|
||||||
// areStatesEqual: shallowCompare,
|
|
||||||
// areOwnPropsEqual: shallowCompare,
|
|
||||||
// areStatePropsEqual: shallowCompare,
|
|
||||||
// areMergedPropsEqual: shallowCompare
|
|
||||||
// };
|
|
||||||
|
|
||||||
export function connect(
|
export function connect(
|
||||||
mapStateToProps?: (state: any, ownProps: { [key: string]: any }) => Object,
|
mapStateToProps?: (state: any, ownProps: { [key: string]: any }) => object,
|
||||||
mapDispatchToProps?:
|
mapDispatchToProps?:
|
||||||
| { [key: string]: (...args: any[]) => ReduxAction }
|
| { [key: string]: (...args: any[]) => ReduxAction }
|
||||||
| ((dispatch: (action: ReduxAction) => any, ownProps?: Object) => Object),
|
| ((dispatch: (action: ReduxAction) => any, ownProps?: object) => object),
|
||||||
mergeProps?: (stateProps: Object, dispatchProps: Object, ownProps: Object) => Object,
|
mergeProps?: (stateProps: object, dispatchProps: object, ownProps: object) => object,
|
||||||
options?: {
|
options?: {
|
||||||
areStatesEqual?: (oldState: any, newState: any) => boolean;
|
areStatesEqual?: (oldState: any, newState: any) => boolean;
|
||||||
context?: any; // TODO: type this
|
context?: any; // TODO: type this
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IObserver } from './Observer';
|
import type { IObserver } from './Observer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一个对象(对象、数组、集合)对应一个Observer
|
* 一个对象(对象、数组、集合)对应一个Observer
|
||||||
|
|
|
@ -123,7 +123,7 @@ export class Observer implements IObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除keyVNodes中保存的这个VNode的关系数据
|
// 删除Observer中保存的这个VNode的关系数据
|
||||||
clearByVNode(vNode: VNode): void {
|
clearByVNode(vNode: VNode): void {
|
||||||
const keys = this.vNodeKeys.get(vNode);
|
const keys = this.vNodeKeys.get(vNode);
|
||||||
if (keys) {
|
if (keys) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { HooklessObserver } from './HooklessObserver';
|
||||||
import { isArray, isCollection, isObject } from '../CommonUtils';
|
import { isArray, isCollection, isObject } from '../CommonUtils';
|
||||||
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
||||||
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
||||||
import { IObserver } from '../types';
|
import type { IObserver } from '../types';
|
||||||
import { OBSERVER_KEY } from '../Constants';
|
import { OBSERVER_KEY } from '../Constants';
|
||||||
|
|
||||||
// 保存rawObj -> Proxy
|
// 保存rawObj -> Proxy
|
||||||
|
|
|
@ -13,150 +13,81 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { useEffect, useRef } from '../../renderer/hooks/HookExternal';
|
import { useEffect, useRef } from '../../renderer/hooks/HookExternal';
|
||||||
import { getProcessingVNode } from '../../renderer/GlobalVar';
|
import { getProcessingVNode } from '../../renderer/GlobalVar';
|
||||||
import { createProxy } from '../proxy/ProxyHandler';
|
import { createProxy } from '../proxy/ProxyHandler';
|
||||||
import readonlyProxy from '../proxy/readonlyProxy';
|
import readonlyProxy from '../proxy/readonlyProxy';
|
||||||
import { Observer } from '../proxy/Observer';
|
import { Observer } from '../proxy/Observer';
|
||||||
import { FunctionComponent, ClassComponent } from '../../renderer/vnode/VNodeTags';
|
import { FunctionComponent, ClassComponent } from '../../renderer/vnode/VNodeTags';
|
||||||
import { VNode } from '../../renderer/Types';
|
|
||||||
import { isPromise } from '../CommonUtils';
|
import { isPromise } from '../CommonUtils';
|
||||||
|
import type {
|
||||||
|
ActionFunction, ComputedValues,
|
||||||
|
PlannedAction, QueuedStoreActions,
|
||||||
|
StoreActions,
|
||||||
|
StoreConfig,
|
||||||
|
StoreObj,
|
||||||
|
UserActions,
|
||||||
|
UserComputedValues
|
||||||
|
} from '../types';
|
||||||
|
import {VNode} from '../../renderer/vnode/VNode';
|
||||||
|
|
||||||
const storeMap = new Map<string, StoreHandler<any, any, any>>();
|
const storeMap = new Map<string, StoreObj<any, any, any>>();
|
||||||
|
|
||||||
type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
|
|
||||||
id?: string;
|
|
||||||
state?: S;
|
|
||||||
actions?: A;
|
|
||||||
computed?: C;
|
|
||||||
options?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UserActions<S extends object> = { [K: string]: ActionFunction<S> };
|
|
||||||
type ActionFunction<S extends object> = (this: StoreHandler<S, any, any>, state: S, ...args: any[]) => any;
|
|
||||||
type StoreActions<S extends object, A extends UserActions<S>> = { [K in keyof A]: Action<A[K], S> };
|
|
||||||
type Action<T extends ActionFunction<any>, S extends object> = (
|
|
||||||
this: StoreHandler<S, any, any>,
|
|
||||||
...args: RemoveFirstFromTuple<Parameters<T>>
|
|
||||||
) => ReturnType<T>;
|
|
||||||
|
|
||||||
type StoreHandler<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
|
|
||||||
$s: S;
|
|
||||||
$a: StoreActions<S, A>;
|
|
||||||
$c: UserComputedValues<S>;
|
|
||||||
$queue: QueuedStoreActions<S, A>;
|
|
||||||
$subscribe: (listener: () => void) => void;
|
|
||||||
$unsubscribe: (listener: () => void) => void;
|
|
||||||
} & { [K in keyof S]: S[K] } & { [K in keyof A]: Action<A[K], S> } & { [K in keyof C]: ReturnType<C[K]> };
|
|
||||||
|
|
||||||
type PlannedAction<S extends object, F extends ActionFunction<S>> = {
|
|
||||||
action: string;
|
|
||||||
payload: any[];
|
|
||||||
resolve: ReturnType<F>;
|
|
||||||
};
|
|
||||||
type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
|
|
||||||
? []
|
|
||||||
: ((...b: T) => void) extends (a, ...b: infer I) => void
|
|
||||||
? I
|
|
||||||
: [];
|
|
||||||
|
|
||||||
type UserComputedValues<S extends object> = { [K: string]: ComputedFunction<S> };
|
|
||||||
|
|
||||||
type ComputedFunction<S extends object> = (state: S) => any;
|
|
||||||
|
|
||||||
type AsyncAction<T extends ActionFunction<any>, S extends object> = (
|
|
||||||
this: StoreHandler<S, any, any>,
|
|
||||||
...args: RemoveFirstFromTuple<Parameters<T>>
|
|
||||||
) => Promise<ReturnType<T>>;
|
|
||||||
|
|
||||||
type QueuedStoreActions<S extends object, A extends UserActions<S>> = { [K in keyof A]: AsyncAction<A[K], S> };
|
|
||||||
type ComputedValues<S extends object, C extends UserComputedValues<S>> = { [K in keyof C]: ReturnType<C[K]> };
|
|
||||||
|
|
||||||
export function createStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
export function createStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
||||||
storeConfig: StoreConfig<S, A, C>
|
config: StoreConfig<S, A, C>
|
||||||
): () => StoreHandler<S, A, C> {
|
): () => StoreObj<S, A, C> {
|
||||||
// 校验
|
// 校验
|
||||||
if (Object.prototype.toString.call(storeConfig) !== '[object Object]') {
|
if (Object.prototype.toString.call(config) !== '[object Object]') {
|
||||||
throw new Error('store obj must be pure object');
|
throw new Error('store obj must be pure object');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建本地浅拷贝以确保一致性(避免用户在创建存储后更改配置对象)
|
const proxyObj = createProxy(config.state, !config.options?.isReduxAdapter);
|
||||||
const config = {
|
|
||||||
id: storeConfig.id,
|
|
||||||
state: storeConfig.state,
|
|
||||||
actions: storeConfig.actions ? { ...storeConfig.actions } : undefined,
|
|
||||||
computed: storeConfig.computed ? { ...storeConfig.computed } : undefined,
|
|
||||||
options: storeConfig.options
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxyObj = createProxy(config.state, !config.options?.reduxAdapter);
|
|
||||||
|
|
||||||
proxyObj.$pending = false;
|
proxyObj.$pending = false;
|
||||||
|
|
||||||
const $subscribe = listener => {
|
|
||||||
proxyObj.addListener(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
const $unsubscribe = listener => {
|
|
||||||
proxyObj.removeListener(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
|
||||||
const $a: Partial<StoreActions<S, A>> = {};
|
const $a: Partial<StoreActions<S, A>> = {};
|
||||||
const $queue: Partial<StoreActions<S, A>> = {};
|
const $queue: Partial<StoreActions<S, A>> = {};
|
||||||
const $c: Partial<ComputedValues<S, C>> = {};
|
const $c: Partial<ComputedValues<S, C>> = {};
|
||||||
const storeHandler = {
|
const storeObj = {
|
||||||
$s: proxyObj,
|
$s: proxyObj,
|
||||||
$a: $a as StoreActions<S, A>,
|
$a: $a as StoreActions<S, A>,
|
||||||
$c: $c as ComputedValues<S, C>,
|
$c: $c as ComputedValues<S, C>,
|
||||||
$queue: $queue as QueuedStoreActions<S, A>,
|
$queue: $queue as QueuedStoreActions<S, A>,
|
||||||
$config: config,
|
$config: config,
|
||||||
$subscribe,
|
$subscribe: listener => {
|
||||||
$unsubscribe,
|
proxyObj.addListener(listener);
|
||||||
} as unknown as StoreHandler<S, A, C>;
|
},
|
||||||
|
$unsubscribe: listener => {
|
||||||
|
proxyObj.removeListener(listener);
|
||||||
|
},
|
||||||
|
} as unknown as StoreObj<S, A, C>;
|
||||||
|
|
||||||
function tryNextAction() {
|
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
||||||
if (!plannedActions.length) {
|
|
||||||
proxyObj.$pending = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextAction = plannedActions.shift()!;
|
|
||||||
const result = config.actions
|
|
||||||
? config.actions[nextAction.action].bind(storeHandler, proxyObj)(...nextAction.payload)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (isPromise(result)) {
|
|
||||||
result.then(value => {
|
|
||||||
nextAction.resolve(value);
|
|
||||||
tryNextAction();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
nextAction.resolve(result);
|
|
||||||
tryNextAction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 包装actions
|
// 包装actions
|
||||||
if (config.actions) {
|
if (config.actions) {
|
||||||
Object.keys(config.actions).forEach(action => {
|
Object.keys(config.actions).forEach(action => {
|
||||||
|
// 让store.$queue[action]可以访问到action方法
|
||||||
|
// 要达到的效果:如果通过store.$queue[action1]调用的action1返回promise,会阻塞下一个store.$queue[action2]
|
||||||
($queue as any)[action] = (...payload) => {
|
($queue as any)[action] = (...payload) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (!proxyObj.$pending) {
|
if (!proxyObj.$pending) {
|
||||||
proxyObj.$pending = true;
|
proxyObj.$pending = true;
|
||||||
const result = config.actions![action].bind(storeHandler, proxyObj)(...payload);
|
|
||||||
|
const result = config.actions![action].bind(storeObj, proxyObj)(...payload);
|
||||||
|
|
||||||
if (isPromise(result)) {
|
if (isPromise(result)) {
|
||||||
result.then(value => {
|
result.then(value => {
|
||||||
resolve(value);
|
resolve(value);
|
||||||
tryNextAction();
|
tryNextAction(storeObj, proxyObj, config, plannedActions);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
tryNextAction();
|
tryNextAction(storeObj, proxyObj, config, plannedActions);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 加入队列
|
||||||
plannedActions.push({
|
plannedActions.push({
|
||||||
action,
|
action,
|
||||||
payload,
|
payload,
|
||||||
|
@ -168,14 +99,14 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
|
||||||
|
|
||||||
// 让store.$a[action]可以访问到action方法
|
// 让store.$a[action]可以访问到action方法
|
||||||
($a as any)[action] = function Wrapped(...payload) {
|
($a as any)[action] = function Wrapped(...payload) {
|
||||||
return config.actions![action].bind(storeHandler, proxyObj)(...payload);
|
return config.actions![action].bind(storeObj, proxyObj)(...payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 让store[action]可以访问到action方法
|
// 让store[action]可以访问到action方法
|
||||||
Object.defineProperty(storeHandler, action, {
|
Object.defineProperty(storeObj, action, {
|
||||||
writable: false,
|
writable: false,
|
||||||
value: (...payload) => {
|
value: (...payload) => {
|
||||||
return config.actions![action].bind(storeHandler, proxyObj)(...payload);
|
return config.actions![action].bind(storeObj, proxyObj)(...payload);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -184,10 +115,10 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
|
||||||
if (config.computed) {
|
if (config.computed) {
|
||||||
Object.keys(config.computed).forEach(computeKey => {
|
Object.keys(config.computed).forEach(computeKey => {
|
||||||
// 让store.$c[computeKey]可以访问到computed方法
|
// 让store.$c[computeKey]可以访问到computed方法
|
||||||
($c as any)[computeKey] = config.computed![computeKey].bind(storeHandler, readonlyProxy(proxyObj));
|
($c as any)[computeKey] = config.computed![computeKey].bind(storeObj, readonlyProxy(proxyObj));
|
||||||
|
|
||||||
// 让store[computeKey]可以访问到computed的值
|
// 让store[computeKey]可以访问到computed的值
|
||||||
Object.defineProperty(storeHandler, computeKey, {
|
Object.defineProperty(storeObj, computeKey, {
|
||||||
get: $c[computeKey] as () => any,
|
get: $c[computeKey] as () => any,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -196,7 +127,7 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
|
||||||
// 让store[key]可以访问到state的值
|
// 让store[key]可以访问到state的值
|
||||||
if (config.state) {
|
if (config.state) {
|
||||||
Object.keys(config.state).forEach(key => {
|
Object.keys(config.state).forEach(key => {
|
||||||
Object.defineProperty(storeHandler, key, {
|
Object.defineProperty(storeObj, key, {
|
||||||
get: () => {
|
get: () => {
|
||||||
// 从Proxy对象获取值,会触发代理
|
// 从Proxy对象获取值,会触发代理
|
||||||
return proxyObj[key];
|
return proxyObj[key];
|
||||||
|
@ -206,14 +137,56 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.id) {
|
if (config.id) {
|
||||||
storeMap.set(config.id, storeHandler);
|
storeMap.set(config.id, storeObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createStoreHook(storeHandler);
|
return createGetStore(storeObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearVNodeObservers(vNode) {
|
// 通过该方法执行store.$queue中的action
|
||||||
if (!vNode.observers) return;
|
function tryNextAction(storeObj, proxyObj, config, plannedActions) {
|
||||||
|
if (!plannedActions.length) {
|
||||||
|
proxyObj.$pending = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextAction = plannedActions.shift()!;
|
||||||
|
const result = config.actions
|
||||||
|
? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (isPromise(result)) {
|
||||||
|
result.then(value => {
|
||||||
|
nextAction.resolve(value);
|
||||||
|
tryNextAction(storeObj, proxyObj, config, plannedActions);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
nextAction.resolve(result);
|
||||||
|
tryNextAction(storeObj, proxyObj, config, plannedActions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createStore返回的是一个getStore的函数,这个函数必须要在组件(函数/类组件)里面被执行,因为要注册VNode销毁时的清理动作
|
||||||
|
function createGetStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
||||||
|
storeObj: StoreObj<S, A, C>
|
||||||
|
): () => StoreObj<S, A, C> {
|
||||||
|
const getStore = () => {
|
||||||
|
if (!storeObj.$config.options?.isReduxAdapter) {
|
||||||
|
registerDestroyFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
return getStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除Observers中保存的这个VNode的相关数据
|
||||||
|
export function clearVNodeObservers(vNode: VNode) {
|
||||||
|
if (!vNode.observers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
vNode.observers.forEach(observer => {
|
vNode.observers.forEach(observer => {
|
||||||
observer.clearByVNode(vNode);
|
observer.clearByVNode(vNode);
|
||||||
});
|
});
|
||||||
|
@ -221,10 +194,11 @@ export function clearVNodeObservers(vNode) {
|
||||||
vNode.observers.clear();
|
vNode.observers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hookStore() {
|
// 注册VNode销毁时的清理动作
|
||||||
|
function registerDestroyFunction() {
|
||||||
const processingVNode = getProcessingVNode();
|
const processingVNode = getProcessingVNode();
|
||||||
|
|
||||||
// did not execute in a component
|
// 获取不到当前运行的VNode,说明不在组件中运行,属于非法场景
|
||||||
if (!processingVNode) {
|
if (!processingVNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -233,8 +207,8 @@ function hookStore() {
|
||||||
processingVNode.observers = new Set<Observer>();
|
processingVNode.observers = new Set<Observer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 函数组件
|
||||||
if (processingVNode.tag === FunctionComponent) {
|
if (processingVNode.tag === FunctionComponent) {
|
||||||
// from FunctionComponent
|
|
||||||
const vNodeRef = useRef(processingVNode);
|
const vNodeRef = useRef(processingVNode);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -243,10 +217,9 @@ function hookStore() {
|
||||||
vNodeRef.current.observers = null;
|
vNodeRef.current.observers = null;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
} else if (processingVNode.tag === ClassComponent) {
|
} else if (processingVNode.tag === ClassComponent) { // 类组件
|
||||||
// from ClassComponent
|
|
||||||
if (!processingVNode.classComponentWillUnmount) {
|
if (!processingVNode.classComponentWillUnmount) {
|
||||||
processingVNode.classComponentWillUnmount = function (vNode) {
|
processingVNode.classComponentWillUnmount = (vNode) => {
|
||||||
clearVNodeObservers(vNode);
|
clearVNodeObservers(vNode);
|
||||||
vNode.observers = null;
|
vNode.observers = null;
|
||||||
};
|
};
|
||||||
|
@ -254,28 +227,17 @@ function hookStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStoreHook<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
// 函数组件中使用的hook
|
||||||
storeHandler: StoreHandler<S, A, C>
|
|
||||||
): () => StoreHandler<S, A, C> {
|
|
||||||
const storeHook = () => {
|
|
||||||
if (!storeHandler.$config.options?.suppressHooks) {
|
|
||||||
hookStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
return storeHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
return storeHook;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
||||||
id: string
|
id: string
|
||||||
): StoreHandler<S, A, C> {
|
): StoreObj<S, A, C> {
|
||||||
const storeObj = storeMap.get(id);
|
const storeObj = storeMap.get(id);
|
||||||
|
|
||||||
if (storeObj && !storeObj.$config.options?.suppressHooks) hookStore();
|
if (storeObj && !storeObj.$config.options?.isReduxAdapter) {
|
||||||
|
registerDestroyFunction();
|
||||||
|
}
|
||||||
|
|
||||||
return storeObj as StoreHandler<S, A, C>;
|
return storeObj as StoreObj<S, A, C>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearStore(id: string): void {
|
export function clearStore(id: string): void {
|
||||||
|
|
|
@ -30,3 +30,68 @@ export interface IObserver {
|
||||||
|
|
||||||
clearByVNode: (vNode: any) => void;
|
clearByVNode: (vNode: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
|
||||||
|
id?: string;
|
||||||
|
state?: S;
|
||||||
|
actions?: A;
|
||||||
|
computed?: C;
|
||||||
|
options?: {
|
||||||
|
isReduxAdapter?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserActions<S extends object> = {
|
||||||
|
[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>
|
||||||
|
};
|
||||||
|
|
||||||
|
type Action<T extends ActionFunction<any>, S extends object> = (
|
||||||
|
this: StoreObj<S, any, any>,
|
||||||
|
...args: RemoveFirstFromTuple<Parameters<T>>
|
||||||
|
) => ReturnType<T>;
|
||||||
|
|
||||||
|
export type StoreObj<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
|
||||||
|
$s: S;
|
||||||
|
$a: StoreActions<S, A>;
|
||||||
|
$c: UserComputedValues<S>;
|
||||||
|
$queue: QueuedStoreActions<S, A>;
|
||||||
|
$subscribe: (listener: () => void) => void;
|
||||||
|
$unsubscribe: (listener: () => 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>> = {
|
||||||
|
action: string;
|
||||||
|
payload: any[];
|
||||||
|
resolve: ReturnType<F>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
|
||||||
|
? []
|
||||||
|
: ((...b: T) => void) extends (a, ...b: infer I) => void
|
||||||
|
? I
|
||||||
|
: [];
|
||||||
|
|
||||||
|
export type UserComputedValues<S extends object> = {
|
||||||
|
[K: string]: ComputedFunction<S>
|
||||||
|
};
|
||||||
|
|
||||||
|
type ComputedFunction<S extends object> = (state: S) => any;
|
||||||
|
|
||||||
|
export type AsyncAction<T extends ActionFunction<any>, S extends object> = (
|
||||||
|
this: StoreObj<S, any, any>,
|
||||||
|
...args: RemoveFirstFromTuple<Parameters<T>>
|
||||||
|
) => Promise<ReturnType<T>>;
|
||||||
|
|
||||||
|
export type QueuedStoreActions<S extends object, A extends UserActions<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]>
|
||||||
|
};
|
||||||
|
|
|
@ -36,6 +36,7 @@ import type { VNodeTag } from './VNodeTags';
|
||||||
import type { RefType, ContextType, SuspenseState, Source } from '../Types';
|
import type { RefType, ContextType, SuspenseState, Source } from '../Types';
|
||||||
import type { Hook } from '../hooks/HookType';
|
import type { Hook } from '../hooks/HookType';
|
||||||
import { InitFlag } from './VNodeFlags';
|
import { InitFlag } from './VNodeFlags';
|
||||||
|
import { Observer } from '../../horizonx/proxy/Observer';
|
||||||
|
|
||||||
export class VNode {
|
export class VNode {
|
||||||
tag: VNodeTag;
|
tag: VNodeTag;
|
||||||
|
@ -100,8 +101,8 @@ export class VNode {
|
||||||
|
|
||||||
// 状态管理器HorizonX使用
|
// 状态管理器HorizonX使用
|
||||||
isStoreChange: boolean;
|
isStoreChange: boolean;
|
||||||
observers: Set<any> | null = null; // 记录这个函数组件/类组件依赖哪些Observer
|
observers: Set<Observer> | null = null; // 记录这个函数组件/类组件依赖哪些Observer
|
||||||
classComponentWillUnmount: Function | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用
|
classComponentWillUnmount: ((vNode: VNode) => any) | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用
|
||||||
src: Source | null; // 节点所在代码位置
|
src: Source | null; // 节点所在代码位置
|
||||||
|
|
||||||
constructor(tag: VNodeTag, props: any, key: null | string, realNode) {
|
constructor(tag: VNodeTag, props: any, key: null | string, realNode) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ function postpone(timer, func) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Asynchronous store', () => {
|
describe('Asynchronous store', () => {
|
||||||
const useAsyncCounter = createStore({
|
const getStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
check: false,
|
check: false,
|
||||||
|
@ -61,13 +61,13 @@ describe('Asynchronous store', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useAsyncCounter().reset();
|
getStore().reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return promise when queued function is called', () => {
|
it('should return promise when queued function is called', () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const store = useAsyncCounter();
|
const store = getStore();
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
store.$queue.increment().then(() => {
|
store.$queue.increment().then(() => {
|
||||||
|
@ -82,9 +82,9 @@ describe('Asynchronous store', () => {
|
||||||
it('should queue async functions', () => {
|
it('should queue async functions', () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const store = useAsyncCounter();
|
const store = getStore();
|
||||||
|
|
||||||
//initial value
|
// initial value
|
||||||
expect(store.value).toBe('false0');
|
expect(store.value).toBe('false0');
|
||||||
|
|
||||||
// no blocking action action
|
// no blocking action action
|
||||||
|
|
Loading…
Reference in New Issue