@chaomingd/store 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/esm/Manager/AsyncManager.d.ts +22 -0
- package/dist/esm/Manager/AsyncManager.js +89 -0
- package/dist/esm/index.d.ts +85 -0
- package/dist/esm/index.js +394 -0
- package/dist/esm/type.d.ts +17 -0
- package/dist/esm/type.js +1 -0
- package/dist/esm/utils/index.d.ts +18 -0
- package/dist/esm/utils/index.js +58 -0
- package/dist/lib/Manager/AsyncManager.d.ts +22 -0
- package/dist/lib/Manager/AsyncManager.js +68 -0
- package/dist/lib/index.d.ts +85 -0
- package/dist/lib/index.js +371 -0
- package/dist/lib/type.d.ts +17 -0
- package/dist/lib/type.js +5 -0
- package/dist/lib/utils/index.d.ts +18 -0
- package/dist/lib/utils/index.js +70 -0
- package/package.json +37 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function calcDiffKeys(obj1, obj2, keys) {
|
|
2
|
+
var diffKeysMap = {};
|
|
3
|
+
var diff = false;
|
|
4
|
+
keys.forEach(function (key) {
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
if (!Object.is(obj1[key], obj2[key])) {
|
|
7
|
+
diffKeysMap[key] = true;
|
|
8
|
+
diff = true;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
diffKeysMap: diffKeysMap,
|
|
13
|
+
diff: diff
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function calcComputedState(_ref) {
|
|
17
|
+
var prevState = _ref.prevState,
|
|
18
|
+
nextState = _ref.nextState,
|
|
19
|
+
computed = _ref.computed;
|
|
20
|
+
if (computed) {
|
|
21
|
+
computed.reduce(function (currentNextState, computedItem) {
|
|
22
|
+
var partialState;
|
|
23
|
+
if (typeof computedItem === 'function') {
|
|
24
|
+
partialState = computedItem(currentNextState, prevState);
|
|
25
|
+
} else {
|
|
26
|
+
var _calcDiffKeys = calcDiffKeys(prevState, currentNextState, computedItem.keys),
|
|
27
|
+
diffKeysMap = _calcDiffKeys.diffKeysMap,
|
|
28
|
+
diff = _calcDiffKeys.diff;
|
|
29
|
+
if (diff) {
|
|
30
|
+
partialState = computedItem.hander(currentNextState, diffKeysMap, prevState);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (partialState) {
|
|
34
|
+
Object.assign(currentNextState, partialState);
|
|
35
|
+
}
|
|
36
|
+
return currentNextState;
|
|
37
|
+
}, nextState);
|
|
38
|
+
}
|
|
39
|
+
return nextState;
|
|
40
|
+
}
|
|
41
|
+
export function execWatchHandler(_ref2) {
|
|
42
|
+
var prevState = _ref2.prevState,
|
|
43
|
+
nextState = _ref2.nextState,
|
|
44
|
+
watch = _ref2.watch;
|
|
45
|
+
if (watch) {
|
|
46
|
+
watch.forEach(function (watchItem) {
|
|
47
|
+
if (watchItem.keys) {
|
|
48
|
+
var _calcDiffKeys2 = calcDiffKeys(prevState, nextState, watchItem.keys),
|
|
49
|
+
diffKeysMap = _calcDiffKeys2.diffKeysMap,
|
|
50
|
+
diff = _calcDiffKeys2.diff;
|
|
51
|
+
if (diff) {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
53
|
+
watchItem.hander && watchItem.hander(nextState, diffKeysMap, prevState);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EventEmitter2 } from '@chaomingd/utils';
|
|
2
|
+
export interface AsyncManagerOptions {
|
|
3
|
+
retryCount?: number;
|
|
4
|
+
retryInterval?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class AsyncManager<T, Fn extends (aborts: {
|
|
7
|
+
lastAbortController: AbortController | null;
|
|
8
|
+
abortController: AbortController;
|
|
9
|
+
}, tryCount: number) => Promise<T>> extends EventEmitter2<{
|
|
10
|
+
loading: (() => void)[];
|
|
11
|
+
success: ((result: any) => void)[];
|
|
12
|
+
error: ((error: Error) => void)[];
|
|
13
|
+
finish: ((error: Error | null, result: any) => void)[];
|
|
14
|
+
}> {
|
|
15
|
+
execId: number;
|
|
16
|
+
options: AsyncManagerOptions;
|
|
17
|
+
abortSignalMap: Record<number, AbortController>;
|
|
18
|
+
constructor(options?: AsyncManagerOptions);
|
|
19
|
+
getCurrentExecId(): number;
|
|
20
|
+
getAbortController(execId: number): AbortController;
|
|
21
|
+
exec(fn: Fn): Promise<T>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.AsyncManager = void 0;
|
|
7
|
+
var _utils = require("@chaomingd/utils");
|
|
8
|
+
const DEFAULT_TIMEOUT = 300;
|
|
9
|
+
class AsyncManager extends _utils.EventEmitter2 {
|
|
10
|
+
execId = 0;
|
|
11
|
+
options = {};
|
|
12
|
+
abortSignalMap = {};
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super();
|
|
15
|
+
if (options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getCurrentExecId() {
|
|
20
|
+
return this.execId;
|
|
21
|
+
}
|
|
22
|
+
getAbortController(execId) {
|
|
23
|
+
return this.abortSignalMap[execId];
|
|
24
|
+
}
|
|
25
|
+
exec(fn) {
|
|
26
|
+
let tryCount = 0;
|
|
27
|
+
const execId = ++this.execId;
|
|
28
|
+
this.emit('loading');
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const _exec = () => {
|
|
31
|
+
const lastAbortController = this.abortSignalMap[execId - 1] || null;
|
|
32
|
+
const abortController = this.abortSignalMap[execId] = new AbortController();
|
|
33
|
+
fn({
|
|
34
|
+
lastAbortController,
|
|
35
|
+
abortController
|
|
36
|
+
}, tryCount).then(res => {
|
|
37
|
+
if (execId === this.execId) {
|
|
38
|
+
this.emit('success', res);
|
|
39
|
+
}
|
|
40
|
+
resolve(res);
|
|
41
|
+
delete this.abortSignalMap[execId];
|
|
42
|
+
this.emit('finish', null, res);
|
|
43
|
+
return res;
|
|
44
|
+
}).catch(e => {
|
|
45
|
+
if (execId === this.execId) {
|
|
46
|
+
if (tryCount < (this.options.retryCount || 0)) {
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
_exec();
|
|
49
|
+
}, this.options.retryInterval || DEFAULT_TIMEOUT);
|
|
50
|
+
} else {
|
|
51
|
+
this.emit('error', e);
|
|
52
|
+
reject(e);
|
|
53
|
+
delete this.abortSignalMap[execId];
|
|
54
|
+
this.emit('finish', e, null);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
delete this.abortSignalMap[execId];
|
|
58
|
+
this.emit('finish', e, null);
|
|
59
|
+
reject(e);
|
|
60
|
+
}
|
|
61
|
+
tryCount++;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
_exec();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.AsyncManager = AsyncManager;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import type { AsyncManagerOptions } from './Manager/AsyncManager';
|
|
3
|
+
import { AsyncManager } from './Manager/AsyncManager';
|
|
4
|
+
import type { IDispatchOptions, TComputed, TEqualityFn, TWatch } from './type';
|
|
5
|
+
type TSubscribeFunc<TState extends Record<string, any> = Record<string, any>, TEffects extends Record<string, any> = Record<string, any>, UserData extends Record<string, any> = Record<string, any>> = (state: Model<TState, TEffects, UserData>, silent: boolean) => any;
|
|
6
|
+
type IEffects<M extends Model<any, any>> = Record<string, ((this: M, ...args: any[]) => any) | any>;
|
|
7
|
+
export interface IModelConfig<TState extends Record<string, any> = Record<string, any>, TEffects extends IEffects<Model<TState, TEffects>> = IEffects<Model>, UserData extends Record<string, any> = Record<string, any>> {
|
|
8
|
+
autoInit?: boolean;
|
|
9
|
+
state: TState;
|
|
10
|
+
effects?: Partial<TEffects>;
|
|
11
|
+
onStateChange?: (prevState: TState, currentState: TState) => any;
|
|
12
|
+
modifyState?: (prevState: TState, nextState: TState) => Partial<TState> | null;
|
|
13
|
+
watch?: TWatch<TState>;
|
|
14
|
+
computed?: TComputed<TState>;
|
|
15
|
+
userData?: UserData;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class Model<TState extends Record<string, any> = Record<string, any>, TEffects extends IEffects<Model<TState, TEffects>> = IEffects<Model<TState, any, any>>, UserData extends Record<string, any> = Record<string, any>> {
|
|
19
|
+
config: IModelConfig<TState, TEffects, UserData>;
|
|
20
|
+
isUnMount: boolean;
|
|
21
|
+
name?: string;
|
|
22
|
+
state: TState;
|
|
23
|
+
_userData: UserData;
|
|
24
|
+
_effects: TEffects;
|
|
25
|
+
_preState: TState;
|
|
26
|
+
_dispatchSignal: string;
|
|
27
|
+
_subscribes: Record<string, TSubscribeFunc<TState, TEffects, UserData>[]>;
|
|
28
|
+
asyncManagerMap: Record<string, AsyncManager<Partial<TState>, (aborts: {
|
|
29
|
+
lastAbortController: AbortController | null;
|
|
30
|
+
abortController: AbortController;
|
|
31
|
+
}, tryCount: number) => Promise<Partial<TState>>>>;
|
|
32
|
+
_isInited: boolean;
|
|
33
|
+
constructor(config: IModelConfig<TState, TEffects, UserData>);
|
|
34
|
+
init(): void;
|
|
35
|
+
asyncManager(name: string, options?: {
|
|
36
|
+
loadingKey?: string;
|
|
37
|
+
errorKey?: string;
|
|
38
|
+
config?: AsyncManagerOptions;
|
|
39
|
+
showLoading?: boolean;
|
|
40
|
+
}): AsyncManager<Partial<TState>, (aborts: {
|
|
41
|
+
lastAbortController: AbortController | null;
|
|
42
|
+
abortController: AbortController;
|
|
43
|
+
}, tryCount: number) => Promise<Partial<TState>>>;
|
|
44
|
+
subscribe(func: TSubscribeFunc<TState, TEffects, UserData>, name?: string): () => void;
|
|
45
|
+
getSubscribeName(name?: string): string;
|
|
46
|
+
unsubscribe(func: TSubscribeFunc<TState, TEffects, UserData>, name?: string): void;
|
|
47
|
+
getUserData(): UserData;
|
|
48
|
+
setUserData(userData: Partial<UserData>): void;
|
|
49
|
+
setState(state: Partial<TState> | ((state: TState) => Partial<TState>), options?: IDispatchOptions): void;
|
|
50
|
+
getActualState(prevState: TState, payload: Partial<TState>): TState & Partial<TState>;
|
|
51
|
+
getState: () => TState;
|
|
52
|
+
dispatch(options?: IDispatchOptions): void;
|
|
53
|
+
setEffect<M extends TEffects[keyof TEffects]>(name: keyof TEffects, effect: M): void;
|
|
54
|
+
setEffects(effects: Partial<TEffects>): void;
|
|
55
|
+
getEffect<Name extends keyof TEffects>(name: Name): (...args: Parameters<TEffects[Name]>) => ReturnType<TEffects[Name]>;
|
|
56
|
+
dispose(): void;
|
|
57
|
+
useSelector: (equalityFn?: TEqualityFn<TState>, name?: string) => TState;
|
|
58
|
+
useGetState: <Key extends keyof TState>(keys?: Key[], equalityFn?: TEqualityFn<TState>, name?: string) => TState;
|
|
59
|
+
useUpdate: <Key extends keyof TState>(keys?: Key[], equalityFn?: TEqualityFn<TState>) => void;
|
|
60
|
+
subscribeWithKeys<Key extends keyof TState>(func: TSubscribeFunc<TState, TEffects>, options: {
|
|
61
|
+
keys?: Key[];
|
|
62
|
+
equalityFn?: TEqualityFn<TState>;
|
|
63
|
+
name?: string;
|
|
64
|
+
}): () => void;
|
|
65
|
+
useSubscribe: <Key extends keyof TState>(func: TSubscribeFunc<TState, TEffects>, options?: {
|
|
66
|
+
keys?: Key[];
|
|
67
|
+
equalityFn?: TEqualityFn<TState>;
|
|
68
|
+
name?: string;
|
|
69
|
+
forceUpdate?: boolean;
|
|
70
|
+
}) => void;
|
|
71
|
+
UI: <Key extends keyof TState>({ children, keys, equalityFn, name, }: {
|
|
72
|
+
children: (state: TState) => ReactNode | ReactNode[];
|
|
73
|
+
keys?: Key[] | undefined;
|
|
74
|
+
equalityFn?: TEqualityFn<TState> | undefined;
|
|
75
|
+
name?: string | undefined;
|
|
76
|
+
}) => ReactNode | ReactNode[];
|
|
77
|
+
}
|
|
78
|
+
export declare function useModel<TState extends Record<string, any>, TEffects extends IEffects<Model<TState, TEffects>> = IEffects<Model<TState>>, UserData extends Record<string, any> = Record<string, any>>(modelConfig: IModelConfig<TState, TEffects, UserData>): Model<TState, TEffects, UserData>;
|
|
79
|
+
export declare function createAsyncEffect<EffectHandler extends (options?: {
|
|
80
|
+
showLoading?: boolean;
|
|
81
|
+
}, ...args: any[]) => Promise<any>>(effectHandler: EffectHandler, config: {
|
|
82
|
+
loadingKey: string;
|
|
83
|
+
}): EffectHandler;
|
|
84
|
+
export declare function createEqualityFn(keys?: string[]): (prevState: Record<string, any>, nextState: Record<string, any>) => boolean;
|
|
85
|
+
export * from './type';
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
Model: true,
|
|
8
|
+
useModel: true,
|
|
9
|
+
createAsyncEffect: true,
|
|
10
|
+
createEqualityFn: true
|
|
11
|
+
};
|
|
12
|
+
exports.Model = void 0;
|
|
13
|
+
exports.createAsyncEffect = createAsyncEffect;
|
|
14
|
+
exports.createEqualityFn = createEqualityFn;
|
|
15
|
+
exports.useModel = useModel;
|
|
16
|
+
var _utils = require("@chaomingd/utils");
|
|
17
|
+
var _ahooks = require("ahooks");
|
|
18
|
+
var _react = require("react");
|
|
19
|
+
var _withSelector = _interopRequireDefault(require("use-sync-external-store/shim/with-selector"));
|
|
20
|
+
var _AsyncManager = require("./Manager/AsyncManager");
|
|
21
|
+
var _utils2 = require("./utils");
|
|
22
|
+
var _type = require("./type");
|
|
23
|
+
Object.keys(_type).forEach(function (key) {
|
|
24
|
+
if (key === "default" || key === "__esModule") return;
|
|
25
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
26
|
+
if (key in exports && exports[key] === _type[key]) return;
|
|
27
|
+
Object.defineProperty(exports, key, {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () {
|
|
30
|
+
return _type[key];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
35
|
+
const {
|
|
36
|
+
useSyncExternalStoreWithSelector
|
|
37
|
+
} = _withSelector.default;
|
|
38
|
+
const DEFAULT_SUBSCRIBE_NAME = 'reactStoreSubscribe';
|
|
39
|
+
class Model {
|
|
40
|
+
isUnMount = false;
|
|
41
|
+
name;
|
|
42
|
+
state = {};
|
|
43
|
+
_userData = {};
|
|
44
|
+
_effects = {};
|
|
45
|
+
_preState = {};
|
|
46
|
+
_dispatchSignal = '';
|
|
47
|
+
_subscribes = {};
|
|
48
|
+
asyncManagerMap = {};
|
|
49
|
+
_isInited = false;
|
|
50
|
+
constructor(config) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
if (config.autoInit !== false) {
|
|
53
|
+
this.init();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
init() {
|
|
57
|
+
if (!this._isInited) {
|
|
58
|
+
this._isInited = true;
|
|
59
|
+
const config = this.config;
|
|
60
|
+
this.state = this.getActualState({}, config.state || {});
|
|
61
|
+
this._preState = {
|
|
62
|
+
...this.state
|
|
63
|
+
};
|
|
64
|
+
this._userData = config?.userData || {};
|
|
65
|
+
if (config.effects) {
|
|
66
|
+
this.setEffects(config.effects);
|
|
67
|
+
}
|
|
68
|
+
if (config.name) {
|
|
69
|
+
this.name = config.name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
asyncManager(name, options) {
|
|
74
|
+
const {
|
|
75
|
+
loadingKey = 'loading',
|
|
76
|
+
errorKey = 'error',
|
|
77
|
+
showLoading = true,
|
|
78
|
+
config
|
|
79
|
+
} = options || {};
|
|
80
|
+
if (!this.asyncManagerMap[name]) {
|
|
81
|
+
this.asyncManagerMap[name] = new _AsyncManager.AsyncManager(config);
|
|
82
|
+
}
|
|
83
|
+
const asyncManager = this.asyncManagerMap[name];
|
|
84
|
+
asyncManager.offAllListeners();
|
|
85
|
+
asyncManager.on('loading', () => {
|
|
86
|
+
if (showLoading) {
|
|
87
|
+
this.setState({
|
|
88
|
+
[loadingKey]: true
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
asyncManager.on('success', result => {
|
|
93
|
+
if (typeof result === 'object' && result !== null) {
|
|
94
|
+
this.setState({
|
|
95
|
+
[loadingKey]: false,
|
|
96
|
+
...result
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
asyncManager.on('error', error => {
|
|
101
|
+
this.setState({
|
|
102
|
+
[loadingKey]: false,
|
|
103
|
+
[errorKey]: error
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
return this.asyncManagerMap[name];
|
|
107
|
+
}
|
|
108
|
+
subscribe(func, name) {
|
|
109
|
+
const subscribeName = this.getSubscribeName(name);
|
|
110
|
+
if (!this._subscribes[subscribeName]) {
|
|
111
|
+
this._subscribes[subscribeName] = [];
|
|
112
|
+
}
|
|
113
|
+
this._subscribes[subscribeName].push(func);
|
|
114
|
+
return () => {
|
|
115
|
+
this.unsubscribe(func, name);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
getSubscribeName(name) {
|
|
119
|
+
return name || DEFAULT_SUBSCRIBE_NAME;
|
|
120
|
+
}
|
|
121
|
+
unsubscribe(func, name) {
|
|
122
|
+
const subscribeName = this.getSubscribeName(name);
|
|
123
|
+
if (this._subscribes[subscribeName] && this._subscribes[subscribeName].length) {
|
|
124
|
+
this._subscribes[subscribeName] = this._subscribes[subscribeName].filter(fn => fn !== func);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
getUserData() {
|
|
128
|
+
return {
|
|
129
|
+
...this._userData
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
setUserData(userData) {
|
|
133
|
+
Object.assign(this._userData, userData);
|
|
134
|
+
}
|
|
135
|
+
setState(state, options) {
|
|
136
|
+
if (state) {
|
|
137
|
+
if (typeof state === 'function') {
|
|
138
|
+
this.state = this.getActualState(this._preState, state(this.state));
|
|
139
|
+
} else {
|
|
140
|
+
this.state = this.getActualState(this._preState, state);
|
|
141
|
+
}
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
143
|
+
this.config.onStateChange && this.config.onStateChange(this._preState, this.getState());
|
|
144
|
+
this.dispatch(options);
|
|
145
|
+
this._preState = {
|
|
146
|
+
...this.state
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
getActualState(prevState, payload) {
|
|
151
|
+
let nextState = {
|
|
152
|
+
...prevState,
|
|
153
|
+
...payload
|
|
154
|
+
};
|
|
155
|
+
const {
|
|
156
|
+
modifyState,
|
|
157
|
+
watch,
|
|
158
|
+
computed
|
|
159
|
+
} = this.config || {};
|
|
160
|
+
let partialState;
|
|
161
|
+
if (modifyState) {
|
|
162
|
+
partialState = modifyState(prevState, nextState);
|
|
163
|
+
if (partialState && typeof partialState === 'object') {
|
|
164
|
+
Object.assign(nextState, partialState);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 处理计算属性
|
|
168
|
+
nextState = (0, _utils2.calcComputedState)({
|
|
169
|
+
prevState,
|
|
170
|
+
nextState,
|
|
171
|
+
computed
|
|
172
|
+
});
|
|
173
|
+
// 执行 watch
|
|
174
|
+
(0, _utils2.execWatchHandler)({
|
|
175
|
+
prevState,
|
|
176
|
+
nextState,
|
|
177
|
+
watch
|
|
178
|
+
});
|
|
179
|
+
return nextState;
|
|
180
|
+
}
|
|
181
|
+
getState = () => {
|
|
182
|
+
return this.state;
|
|
183
|
+
};
|
|
184
|
+
dispatch(options) {
|
|
185
|
+
if (this.isUnMount) return;
|
|
186
|
+
let subscribeNames = Object.keys(this._subscribes);
|
|
187
|
+
if (options) {
|
|
188
|
+
if (options.include) {
|
|
189
|
+
const includeNameMap = (0, _utils.arrayToMap)(options.include);
|
|
190
|
+
subscribeNames = subscribeNames.filter(name => includeNameMap[name]);
|
|
191
|
+
}
|
|
192
|
+
if (options.exclude) {
|
|
193
|
+
const excludeNameMap = (0, _utils.arrayToMap)(options.exclude);
|
|
194
|
+
subscribeNames = subscribeNames.filter(name => !excludeNameMap[name]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this._dispatchSignal = (0, _utils.uuid)();
|
|
198
|
+
subscribeNames.forEach(subscribeName => {
|
|
199
|
+
if (this._subscribes[subscribeName]) {
|
|
200
|
+
this._subscribes[subscribeName].forEach(func => func(this, options?.silent || false));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
setEffect(name, effect) {
|
|
205
|
+
if (this._effects[name] !== effect) {
|
|
206
|
+
this._effects[name] = typeof effect === 'function' ? effect.bind(this) : effect;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
setEffects(effects) {
|
|
210
|
+
Object.keys(effects).forEach(name => {
|
|
211
|
+
this.setEffect(name, effects[name]);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
getEffect(name) {
|
|
215
|
+
return (...args) => {
|
|
216
|
+
return this._effects[name].apply(this, args);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
dispose() {
|
|
220
|
+
this._effects = {};
|
|
221
|
+
this.state = {};
|
|
222
|
+
}
|
|
223
|
+
useSelector = (equalityFn, name) => {
|
|
224
|
+
// eslint-disable-next-line
|
|
225
|
+
const subscribe = (0, _ahooks.useMemoizedFn)(listener => {
|
|
226
|
+
return this.subscribe((_, silent) => {
|
|
227
|
+
if (!silent) {
|
|
228
|
+
listener();
|
|
229
|
+
}
|
|
230
|
+
}, name);
|
|
231
|
+
});
|
|
232
|
+
const selector = (0, _ahooks.useMemoizedFn)(state => state);
|
|
233
|
+
const isEqual = (0, _ahooks.useMemoizedFn)((prevState, nextState) => {
|
|
234
|
+
if (equalityFn) {
|
|
235
|
+
return equalityFn(prevState, nextState);
|
|
236
|
+
}
|
|
237
|
+
return Object.is(prevState, nextState);
|
|
238
|
+
});
|
|
239
|
+
const state = useSyncExternalStoreWithSelector(subscribe, this.getState, this.getState, selector, isEqual);
|
|
240
|
+
return state;
|
|
241
|
+
};
|
|
242
|
+
useGetState = (keys, equalityFn, name) => {
|
|
243
|
+
const state = this.useSelector((prevState, nextState) => {
|
|
244
|
+
if (keys && (0, _utils.shallowEqualKeys)(prevState, nextState, keys)) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (equalityFn && equalityFn(prevState, nextState)) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}, name);
|
|
252
|
+
return state;
|
|
253
|
+
};
|
|
254
|
+
useUpdate = (keys, equalityFn) => {
|
|
255
|
+
this.useGetState(keys, equalityFn);
|
|
256
|
+
};
|
|
257
|
+
subscribeWithKeys(func, options) {
|
|
258
|
+
const {
|
|
259
|
+
keys,
|
|
260
|
+
equalityFn,
|
|
261
|
+
name
|
|
262
|
+
} = options;
|
|
263
|
+
return this.subscribe((_, silent) => {
|
|
264
|
+
const nextState = this.getState();
|
|
265
|
+
if (keys && (0, _utils.shallowEqualKeys)(this._preState, nextState, keys)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (equalityFn && equalityFn(this._preState, nextState)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
func(this, silent);
|
|
272
|
+
}, name);
|
|
273
|
+
}
|
|
274
|
+
useSubscribe = (func, options) => {
|
|
275
|
+
const unmountRef = (0, _react.useRef)(false);
|
|
276
|
+
const funcRef = (0, _ahooks.useLatest)(func);
|
|
277
|
+
// eslint-disable-next-line
|
|
278
|
+
const [_, forceUpdate] = (0, _react.useState)({});
|
|
279
|
+
const optionsRef = (0, _ahooks.useLatest)(options);
|
|
280
|
+
const needDispatchRef = (0, _react.useRef)(false);
|
|
281
|
+
(0, _react.useEffect)(() => {
|
|
282
|
+
unmountRef.current = false;
|
|
283
|
+
funcRef.current?.(this, false);
|
|
284
|
+
const unsubscribe = this.subscribeWithKeys((__, silent) => {
|
|
285
|
+
if (!unmountRef.current) {
|
|
286
|
+
needDispatchRef.current = true;
|
|
287
|
+
if (optionsRef.current?.forceUpdate !== false && silent !== true) {
|
|
288
|
+
forceUpdate({});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}, options || {});
|
|
292
|
+
return () => {
|
|
293
|
+
unmountRef.current = true;
|
|
294
|
+
unsubscribe();
|
|
295
|
+
};
|
|
296
|
+
// eslint-disable-next-line
|
|
297
|
+
}, []);
|
|
298
|
+
(0, _react.useEffect)(() => {
|
|
299
|
+
if (needDispatchRef.current) {
|
|
300
|
+
funcRef.current?.(this, false);
|
|
301
|
+
needDispatchRef.current = false;
|
|
302
|
+
}
|
|
303
|
+
}, [this._dispatchSignal, funcRef]);
|
|
304
|
+
};
|
|
305
|
+
UI = ({
|
|
306
|
+
children,
|
|
307
|
+
keys,
|
|
308
|
+
equalityFn,
|
|
309
|
+
name
|
|
310
|
+
}) => {
|
|
311
|
+
const state = this.useGetState(keys, equalityFn, name);
|
|
312
|
+
return children(state);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
exports.Model = Model;
|
|
316
|
+
function useModel(modelConfig) {
|
|
317
|
+
const model = (0, _ahooks.useCreation)(() => {
|
|
318
|
+
return new Model(modelConfig);
|
|
319
|
+
}, []);
|
|
320
|
+
model.config = modelConfig;
|
|
321
|
+
if (modelConfig.effects) {
|
|
322
|
+
model.setEffects(modelConfig.effects);
|
|
323
|
+
}
|
|
324
|
+
(0, _react.useEffect)(() => {
|
|
325
|
+
model.isUnMount = false;
|
|
326
|
+
return () => {
|
|
327
|
+
model.isUnMount = true;
|
|
328
|
+
};
|
|
329
|
+
}, [model]);
|
|
330
|
+
return model;
|
|
331
|
+
}
|
|
332
|
+
function createAsyncEffect(effectHandler, config) {
|
|
333
|
+
return function (...args) {
|
|
334
|
+
const options = args[0];
|
|
335
|
+
const loadingKey = config?.loadingKey || 'loading';
|
|
336
|
+
const showLoading = options?.showLoading;
|
|
337
|
+
const model = this;
|
|
338
|
+
if (showLoading !== false) {
|
|
339
|
+
model.setState({
|
|
340
|
+
[loadingKey]: true
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const fetchCountKey = `${loadingKey}-count`;
|
|
344
|
+
if (model[fetchCountKey] === undefined) {
|
|
345
|
+
model[fetchCountKey] = -1;
|
|
346
|
+
}
|
|
347
|
+
const fetchCount = ++model[fetchCountKey];
|
|
348
|
+
return effectHandler(...args).then(data => {
|
|
349
|
+
if (fetchCount !== model[fetchCountKey]) return data;
|
|
350
|
+
const nextState = {};
|
|
351
|
+
if (showLoading !== false) {
|
|
352
|
+
nextState[loadingKey] = false;
|
|
353
|
+
}
|
|
354
|
+
if (data && typeof data === 'object') {
|
|
355
|
+
Object.assign(nextState, data);
|
|
356
|
+
}
|
|
357
|
+
model.setState(nextState);
|
|
358
|
+
return data;
|
|
359
|
+
}).catch(e => {
|
|
360
|
+
if (showLoading !== false) {
|
|
361
|
+
model.setState({
|
|
362
|
+
[loadingKey]: false
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
throw e;
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function createEqualityFn(keys) {
|
|
370
|
+
return (prevState, nextState) => (0, _utils.shallowEqualKeys)(prevState, nextState, keys);
|
|
371
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type TEqualityFn<TState extends Record<string, any>> = (prevState: TState, nextState: TState) => boolean;
|
|
2
|
+
export type TPromiseValue<T> = T | Promise<T>;
|
|
3
|
+
export type TComputedHandler<TState, R = any> = (state: TState, keyMap: Record<keyof TState & string, boolean>, prevState: TState) => R;
|
|
4
|
+
export type TWatch<TState extends Record<string, any>> = {
|
|
5
|
+
keys: (keyof TState)[];
|
|
6
|
+
hander: TComputedHandler<TState, any>;
|
|
7
|
+
}[];
|
|
8
|
+
export type TComputed<TState extends Record<string, any>> = ({
|
|
9
|
+
keys: (keyof TState)[];
|
|
10
|
+
hander: TComputedHandler<TState, Partial<TState>>;
|
|
11
|
+
} | ((state: TState, prevState: TState) => Partial<TState>))[];
|
|
12
|
+
export interface IDispatchOptions {
|
|
13
|
+
include?: string[];
|
|
14
|
+
exclude?: string[];
|
|
15
|
+
silent?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type TSelectorFn<TState> = (state: TState) => Partial<TState>;
|
package/dist/lib/type.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TComputed, TWatch } from '../type';
|
|
2
|
+
interface IComputedConfig<TState extends Record<string, any>> {
|
|
3
|
+
prevState: TState;
|
|
4
|
+
nextState: TState;
|
|
5
|
+
computed?: TComputed<TState>;
|
|
6
|
+
}
|
|
7
|
+
export declare function calcDiffKeys(obj1: object, obj2: object, keys: (string | number | symbol)[]): {
|
|
8
|
+
diffKeysMap: Record<string | number | symbol, boolean>;
|
|
9
|
+
diff: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function calcComputedState<TState extends Record<string, any>>({ prevState, nextState, computed, }: IComputedConfig<TState>): TState;
|
|
12
|
+
interface IWatchConfig<TState extends Record<string, any>> {
|
|
13
|
+
prevState: TState;
|
|
14
|
+
nextState: TState;
|
|
15
|
+
watch?: TWatch<TState>;
|
|
16
|
+
}
|
|
17
|
+
export declare function execWatchHandler<TState extends Record<string, any>>({ prevState, nextState, watch, }: IWatchConfig<TState>): void;
|
|
18
|
+
export {};
|