@canlooks/statio 1.0.0
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/README.md +511 -0
- package/dist/cjs/api.js +51 -0
- package/dist/cjs/compute.js +51 -0
- package/dist/cjs/createStore.js +104 -0
- package/dist/cjs/index.js +8 -0
- package/dist/cjs/log.js +4 -0
- package/dist/cjs/storage.js +44 -0
- package/dist/cjs/util.js +110 -0
- package/dist/esm/api.js +47 -0
- package/dist/esm/compute.js +46 -0
- package/dist/esm/createStore.js +101 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/log.js +1 -0
- package/dist/esm/storage.js +41 -0
- package/dist/esm/util.js +103 -0
- package/index.d.ts +79 -0
- package/package.json +58 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStore = createStore;
|
|
4
|
+
const api_1 = require("./api");
|
|
5
|
+
const util_1 = require("./util");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
function createStore(factory) {
|
|
8
|
+
const api = new api_1.Api(factory, state => {
|
|
9
|
+
for (const fire of listeners) {
|
|
10
|
+
fire(state);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
function useStore(a, b, ...rest) {
|
|
14
|
+
let selector;
|
|
15
|
+
let isEqual;
|
|
16
|
+
if (typeof a !== 'undefined') {
|
|
17
|
+
if (typeof a === 'function') {
|
|
18
|
+
selector = a;
|
|
19
|
+
isEqual = b;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const keys = [a, b, ...rest];
|
|
23
|
+
selector = (state) => {
|
|
24
|
+
const select = {};
|
|
25
|
+
keys.forEach((key) => {
|
|
26
|
+
if (key in state) {
|
|
27
|
+
select[key] = state[key];
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return select;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const symbolHelper = (0, react_1.useRef)(Symbol());
|
|
35
|
+
const cachedSnapshot = (0, react_1.useRef)(void 0);
|
|
36
|
+
if (selector) {
|
|
37
|
+
cachedSnapshot.current ||= selector(api.state);
|
|
38
|
+
}
|
|
39
|
+
const result = (0, react_1.useSyncExternalStore)(onStoreChange => {
|
|
40
|
+
const listener = (snapshot) => {
|
|
41
|
+
if (selector) {
|
|
42
|
+
cachedSnapshot.current = snapshot;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
symbolHelper.current = Symbol();
|
|
46
|
+
}
|
|
47
|
+
onStoreChange();
|
|
48
|
+
};
|
|
49
|
+
return selector
|
|
50
|
+
? subscribe(selector, listener, { isEqual, _initSnapshot: cachedSnapshot.current })
|
|
51
|
+
: subscribe(listener);
|
|
52
|
+
}, () => selector ? cachedSnapshot.current : symbolHelper.current, () => api.serverState);
|
|
53
|
+
return typeof result === 'symbol' ? api.state : result;
|
|
54
|
+
}
|
|
55
|
+
const listeners = new Set();
|
|
56
|
+
const originListener_callback = new WeakMap();
|
|
57
|
+
function subscribe(a, b, c) {
|
|
58
|
+
const bIsFunction = typeof b === 'function';
|
|
59
|
+
const listener = bIsFunction ? b : a;
|
|
60
|
+
const selector = bIsFunction ? a : void 0;
|
|
61
|
+
const options = bIsFunction ? c : b;
|
|
62
|
+
let prevSnapshot = options?._initSnapshot;
|
|
63
|
+
const callback = () => {
|
|
64
|
+
if (!selector) {
|
|
65
|
+
listener(api.state);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const snapShot = selector(api.state);
|
|
69
|
+
if (options?.isEqual) {
|
|
70
|
+
if (options.isEqual(snapShot, prevSnapshot)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (typeof snapShot === 'object' && snapShot !== null) {
|
|
75
|
+
if ((0, util_1.shallowEqual)(snapShot, prevSnapshot)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (prevSnapshot === snapShot) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
listener(snapShot, prevSnapshot);
|
|
83
|
+
prevSnapshot = snapShot;
|
|
84
|
+
};
|
|
85
|
+
options?.immediate && callback();
|
|
86
|
+
listeners.add(callback);
|
|
87
|
+
originListener_callback.set(listener, callback);
|
|
88
|
+
return () => {
|
|
89
|
+
unsubscribe(listener);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function unsubscribe(listener) {
|
|
93
|
+
const callback = originListener_callback.get(listener);
|
|
94
|
+
if (callback) {
|
|
95
|
+
listeners.delete(callback);
|
|
96
|
+
originListener_callback.delete(listener);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
useStore.getState = () => api.state;
|
|
100
|
+
useStore.setState = api.setState;
|
|
101
|
+
useStore.subscribe = subscribe;
|
|
102
|
+
useStore.unsubscribe = unsubscribe;
|
|
103
|
+
return useStore;
|
|
104
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./api"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./compute"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./createStore"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./storage"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./util"), exports);
|
package/dist/cjs/log.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storage = storage;
|
|
4
|
+
const util_1 = require("./util");
|
|
5
|
+
const api_1 = require("./api");
|
|
6
|
+
function storage(factory, options) {
|
|
7
|
+
return (set, api) => {
|
|
8
|
+
const { name, type = 'localStorage', selector = state => {
|
|
9
|
+
const selected = {};
|
|
10
|
+
const descriptors = (0, util_1.getAllPropertyDescriptors)(state);
|
|
11
|
+
for (const k in descriptors) {
|
|
12
|
+
const { value } = descriptors[k];
|
|
13
|
+
if (typeof value !== 'function' && !(value instanceof api_1.Api)) {
|
|
14
|
+
selected[k] = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return selected;
|
|
18
|
+
}, adapter } = options;
|
|
19
|
+
const storageFactory = adapter || (typeof window !== 'undefined' ? window[type] : void 0);
|
|
20
|
+
const batchSet = (0, util_1.createBatchAction)(set, () => {
|
|
21
|
+
if (storageFactory) {
|
|
22
|
+
const state = api.getState();
|
|
23
|
+
const value = selector ? selector(state) : state;
|
|
24
|
+
storageFactory.setItem(name, JSON.stringify(value));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const state = (0, util_1.isClass)(factory)
|
|
28
|
+
? new factory(batchSet, api)
|
|
29
|
+
: factory(batchSet, api);
|
|
30
|
+
api.serverState = { ...state };
|
|
31
|
+
if (storageFactory) {
|
|
32
|
+
const value = storageFactory.getItem(name);
|
|
33
|
+
if (value !== null) {
|
|
34
|
+
try {
|
|
35
|
+
const cached = JSON.parse(value);
|
|
36
|
+
Object.assign(state, cached);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return state;
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/cjs/util.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAllPropertyDescriptors = getAllPropertyDescriptors;
|
|
4
|
+
exports.isClass = isClass;
|
|
5
|
+
exports.shallowEqual = shallowEqual;
|
|
6
|
+
exports.nextTick = nextTick;
|
|
7
|
+
exports.createBatchAction = createBatchAction;
|
|
8
|
+
/**
|
|
9
|
+
* 得到所有属性的描述符,包括被继承的父类
|
|
10
|
+
* @param o
|
|
11
|
+
*/
|
|
12
|
+
function getAllPropertyDescriptors(o) {
|
|
13
|
+
const { constructor, ...desc } = Object.getOwnPropertyDescriptors(o);
|
|
14
|
+
const prototype = Object.getPrototypeOf(o);
|
|
15
|
+
if (prototype !== Object.prototype && prototype !== Array.prototype && prototype !== Function.prototype) {
|
|
16
|
+
return {
|
|
17
|
+
...getAllPropertyDescriptors(prototype),
|
|
18
|
+
...desc
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return desc;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 判断一个函数是否为Class
|
|
25
|
+
* @param fn
|
|
26
|
+
*/
|
|
27
|
+
function isClass(fn) {
|
|
28
|
+
if (fn.prototype?.constructor !== fn) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return Function.prototype.toString.call(fn).startsWith('class');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 浅比较,判断对象或数组是否“相等”
|
|
35
|
+
* @param a
|
|
36
|
+
* @param b
|
|
37
|
+
*/
|
|
38
|
+
function shallowEqual(a, b) {
|
|
39
|
+
if (a === b) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (typeof a !== 'object') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (a === null) {
|
|
46
|
+
return b === null;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const aKeys = Object.keys(a);
|
|
52
|
+
if (aKeys.length !== Object.keys(b).length) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
let isEqual = true;
|
|
56
|
+
for (let i = 0, { length } = aKeys; i < length; i++) {
|
|
57
|
+
const k = aKeys[i];
|
|
58
|
+
if (!(k in b) || a[k] !== b[k]) {
|
|
59
|
+
isEqual = false;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return isEqual;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 下一个事件循环
|
|
67
|
+
* @param callback
|
|
68
|
+
* @param args
|
|
69
|
+
*/
|
|
70
|
+
function nextTick(callback, ...args) {
|
|
71
|
+
let aborted = false;
|
|
72
|
+
const promise = new Promise(resolve => {
|
|
73
|
+
if (typeof queueMicrotask === 'function') {
|
|
74
|
+
queueMicrotask(fn);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (typeof process === 'object' && process.nextTick) {
|
|
78
|
+
process.nextTick(fn, ...args);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setTimeout(fn, 0, ...args);
|
|
82
|
+
function fn(...a) {
|
|
83
|
+
if (!aborted) {
|
|
84
|
+
callback?.(...a);
|
|
85
|
+
resolve(a[0]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
promise.abort = () => {
|
|
90
|
+
aborted = true;
|
|
91
|
+
};
|
|
92
|
+
return promise;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 多次同步动作仅触发一次副作用
|
|
96
|
+
* @param action
|
|
97
|
+
* @param effect
|
|
98
|
+
*/
|
|
99
|
+
function createBatchAction(action, effect) {
|
|
100
|
+
let prevPending;
|
|
101
|
+
return function (...args) {
|
|
102
|
+
try {
|
|
103
|
+
action.apply(this, args);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
prevPending?.abort();
|
|
107
|
+
prevPending = nextTick(effect);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
package/dist/esm/api.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Computable } from './compute.js';
|
|
2
|
+
import { getAllPropertyDescriptors, isClass } from './util.js';
|
|
3
|
+
export class Api {
|
|
4
|
+
onChange;
|
|
5
|
+
serverState;
|
|
6
|
+
state;
|
|
7
|
+
computable;
|
|
8
|
+
constructor(factory, onChange) {
|
|
9
|
+
this.onChange = onChange;
|
|
10
|
+
this.state = isClass(factory)
|
|
11
|
+
? new factory(this.setState, this)
|
|
12
|
+
: factory(this.setState, this);
|
|
13
|
+
this.bindContextAndInitComputable();
|
|
14
|
+
}
|
|
15
|
+
setState = (setStateAction, overwrite) => {
|
|
16
|
+
const newState = typeof setStateAction === 'function' ? setStateAction(this.state) : setStateAction;
|
|
17
|
+
if (overwrite) {
|
|
18
|
+
this.state = newState;
|
|
19
|
+
this.bindContextAndInitComputable();
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
Object.assign(this.state, newState);
|
|
23
|
+
}
|
|
24
|
+
this.onChange(this.state);
|
|
25
|
+
};
|
|
26
|
+
getState = () => {
|
|
27
|
+
return this.state;
|
|
28
|
+
};
|
|
29
|
+
compute = (factory, deps) => {
|
|
30
|
+
return this.computable.get(factory, deps);
|
|
31
|
+
};
|
|
32
|
+
bindContextAndInitComputable() {
|
|
33
|
+
this.computable = new Computable(this.state);
|
|
34
|
+
const properties = getAllPropertyDescriptors(this.state);
|
|
35
|
+
for (const k in properties) {
|
|
36
|
+
const { get, value } = properties[k];
|
|
37
|
+
if (get) {
|
|
38
|
+
Object.defineProperty(this.state, k, {
|
|
39
|
+
get: this.computable.createGetter(k, () => get.call(this.state))
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else if (typeof value === 'function') {
|
|
43
|
+
this.state[k] = value.bind(this.state);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { shallowEqual } from './util.js';
|
|
2
|
+
import { prefix } from './log.js';
|
|
3
|
+
export function createCompute() {
|
|
4
|
+
let prevDeps = [];
|
|
5
|
+
let prevResult;
|
|
6
|
+
let hasRun = false;
|
|
7
|
+
return function (factory, deps) {
|
|
8
|
+
if (hasRun && shallowEqual(prevDeps, deps)) {
|
|
9
|
+
return prevResult;
|
|
10
|
+
}
|
|
11
|
+
hasRun = true;
|
|
12
|
+
prevDeps = [...deps];
|
|
13
|
+
return prevResult = factory.call(this);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export class Computable {
|
|
17
|
+
state;
|
|
18
|
+
constructor(state) {
|
|
19
|
+
this.state = state;
|
|
20
|
+
}
|
|
21
|
+
property_compute = new Map();
|
|
22
|
+
stack = [];
|
|
23
|
+
createGetter(key, get) {
|
|
24
|
+
return () => {
|
|
25
|
+
try {
|
|
26
|
+
this.stack.push(key);
|
|
27
|
+
return get();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
this.stack.pop();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
get(factory, deps) {
|
|
35
|
+
const key = this.stack[this.stack.length - 1];
|
|
36
|
+
if (!key) {
|
|
37
|
+
throw Error(`${prefix}"compute" method can only be used in getter properties.`);
|
|
38
|
+
}
|
|
39
|
+
let compute = this.property_compute.get(key);
|
|
40
|
+
if (!compute) {
|
|
41
|
+
compute = createCompute();
|
|
42
|
+
this.property_compute.set(key, compute);
|
|
43
|
+
}
|
|
44
|
+
return compute.call(this.state, factory, deps);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Api } from './api.js';
|
|
2
|
+
import { shallowEqual } from './util.js';
|
|
3
|
+
import { useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
export function createStore(factory) {
|
|
5
|
+
const api = new Api(factory, state => {
|
|
6
|
+
for (const fire of listeners) {
|
|
7
|
+
fire(state);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
function useStore(a, b, ...rest) {
|
|
11
|
+
let selector;
|
|
12
|
+
let isEqual;
|
|
13
|
+
if (typeof a !== 'undefined') {
|
|
14
|
+
if (typeof a === 'function') {
|
|
15
|
+
selector = a;
|
|
16
|
+
isEqual = b;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const keys = [a, b, ...rest];
|
|
20
|
+
selector = (state) => {
|
|
21
|
+
const select = {};
|
|
22
|
+
keys.forEach((key) => {
|
|
23
|
+
if (key in state) {
|
|
24
|
+
select[key] = state[key];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return select;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const symbolHelper = useRef(Symbol());
|
|
32
|
+
const cachedSnapshot = useRef(void 0);
|
|
33
|
+
if (selector) {
|
|
34
|
+
cachedSnapshot.current ||= selector(api.state);
|
|
35
|
+
}
|
|
36
|
+
const result = useSyncExternalStore(onStoreChange => {
|
|
37
|
+
const listener = (snapshot) => {
|
|
38
|
+
if (selector) {
|
|
39
|
+
cachedSnapshot.current = snapshot;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
symbolHelper.current = Symbol();
|
|
43
|
+
}
|
|
44
|
+
onStoreChange();
|
|
45
|
+
};
|
|
46
|
+
return selector
|
|
47
|
+
? subscribe(selector, listener, { isEqual, _initSnapshot: cachedSnapshot.current })
|
|
48
|
+
: subscribe(listener);
|
|
49
|
+
}, () => selector ? cachedSnapshot.current : symbolHelper.current, () => api.serverState);
|
|
50
|
+
return typeof result === 'symbol' ? api.state : result;
|
|
51
|
+
}
|
|
52
|
+
const listeners = new Set();
|
|
53
|
+
const originListener_callback = new WeakMap();
|
|
54
|
+
function subscribe(a, b, c) {
|
|
55
|
+
const bIsFunction = typeof b === 'function';
|
|
56
|
+
const listener = bIsFunction ? b : a;
|
|
57
|
+
const selector = bIsFunction ? a : void 0;
|
|
58
|
+
const options = bIsFunction ? c : b;
|
|
59
|
+
let prevSnapshot = options?._initSnapshot;
|
|
60
|
+
const callback = () => {
|
|
61
|
+
if (!selector) {
|
|
62
|
+
listener(api.state);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const snapShot = selector(api.state);
|
|
66
|
+
if (options?.isEqual) {
|
|
67
|
+
if (options.isEqual(snapShot, prevSnapshot)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (typeof snapShot === 'object' && snapShot !== null) {
|
|
72
|
+
if (shallowEqual(snapShot, prevSnapshot)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (prevSnapshot === snapShot) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
listener(snapShot, prevSnapshot);
|
|
80
|
+
prevSnapshot = snapShot;
|
|
81
|
+
};
|
|
82
|
+
options?.immediate && callback();
|
|
83
|
+
listeners.add(callback);
|
|
84
|
+
originListener_callback.set(listener, callback);
|
|
85
|
+
return () => {
|
|
86
|
+
unsubscribe(listener);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function unsubscribe(listener) {
|
|
90
|
+
const callback = originListener_callback.get(listener);
|
|
91
|
+
if (callback) {
|
|
92
|
+
listeners.delete(callback);
|
|
93
|
+
originListener_callback.delete(listener);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
useStore.getState = () => api.state;
|
|
97
|
+
useStore.setState = api.setState;
|
|
98
|
+
useStore.subscribe = subscribe;
|
|
99
|
+
useStore.unsubscribe = unsubscribe;
|
|
100
|
+
return useStore;
|
|
101
|
+
}
|
package/dist/esm/log.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const prefix = '[@canlooks/statio] ';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createBatchAction, getAllPropertyDescriptors, isClass } from './util.js';
|
|
2
|
+
import { Api } from './api.js';
|
|
3
|
+
export function storage(factory, options) {
|
|
4
|
+
return (set, api) => {
|
|
5
|
+
const { name, type = 'localStorage', selector = state => {
|
|
6
|
+
const selected = {};
|
|
7
|
+
const descriptors = getAllPropertyDescriptors(state);
|
|
8
|
+
for (const k in descriptors) {
|
|
9
|
+
const { value } = descriptors[k];
|
|
10
|
+
if (typeof value !== 'function' && !(value instanceof Api)) {
|
|
11
|
+
selected[k] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return selected;
|
|
15
|
+
}, adapter } = options;
|
|
16
|
+
const storageFactory = adapter || (typeof window !== 'undefined' ? window[type] : void 0);
|
|
17
|
+
const batchSet = createBatchAction(set, () => {
|
|
18
|
+
if (storageFactory) {
|
|
19
|
+
const state = api.getState();
|
|
20
|
+
const value = selector ? selector(state) : state;
|
|
21
|
+
storageFactory.setItem(name, JSON.stringify(value));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const state = isClass(factory)
|
|
25
|
+
? new factory(batchSet, api)
|
|
26
|
+
: factory(batchSet, api);
|
|
27
|
+
api.serverState = { ...state };
|
|
28
|
+
if (storageFactory) {
|
|
29
|
+
const value = storageFactory.getItem(name);
|
|
30
|
+
if (value !== null) {
|
|
31
|
+
try {
|
|
32
|
+
const cached = JSON.parse(value);
|
|
33
|
+
Object.assign(state, cached);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return state;
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/esm/util.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 得到所有属性的描述符,包括被继承的父类
|
|
3
|
+
* @param o
|
|
4
|
+
*/
|
|
5
|
+
export function getAllPropertyDescriptors(o) {
|
|
6
|
+
const { constructor, ...desc } = Object.getOwnPropertyDescriptors(o);
|
|
7
|
+
const prototype = Object.getPrototypeOf(o);
|
|
8
|
+
if (prototype !== Object.prototype && prototype !== Array.prototype && prototype !== Function.prototype) {
|
|
9
|
+
return {
|
|
10
|
+
...getAllPropertyDescriptors(prototype),
|
|
11
|
+
...desc
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return desc;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 判断一个函数是否为Class
|
|
18
|
+
* @param fn
|
|
19
|
+
*/
|
|
20
|
+
export function isClass(fn) {
|
|
21
|
+
if (fn.prototype?.constructor !== fn) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return Function.prototype.toString.call(fn).startsWith('class');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 浅比较,判断对象或数组是否“相等”
|
|
28
|
+
* @param a
|
|
29
|
+
* @param b
|
|
30
|
+
*/
|
|
31
|
+
export function shallowEqual(a, b) {
|
|
32
|
+
if (a === b) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (typeof a !== 'object') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (a === null) {
|
|
39
|
+
return b === null;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const aKeys = Object.keys(a);
|
|
45
|
+
if (aKeys.length !== Object.keys(b).length) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
let isEqual = true;
|
|
49
|
+
for (let i = 0, { length } = aKeys; i < length; i++) {
|
|
50
|
+
const k = aKeys[i];
|
|
51
|
+
if (!(k in b) || a[k] !== b[k]) {
|
|
52
|
+
isEqual = false;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return isEqual;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 下一个事件循环
|
|
60
|
+
* @param callback
|
|
61
|
+
* @param args
|
|
62
|
+
*/
|
|
63
|
+
export function nextTick(callback, ...args) {
|
|
64
|
+
let aborted = false;
|
|
65
|
+
const promise = new Promise(resolve => {
|
|
66
|
+
if (typeof queueMicrotask === 'function') {
|
|
67
|
+
queueMicrotask(fn);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof process === 'object' && process.nextTick) {
|
|
71
|
+
process.nextTick(fn, ...args);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setTimeout(fn, 0, ...args);
|
|
75
|
+
function fn(...a) {
|
|
76
|
+
if (!aborted) {
|
|
77
|
+
callback?.(...a);
|
|
78
|
+
resolve(a[0]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
promise.abort = () => {
|
|
83
|
+
aborted = true;
|
|
84
|
+
};
|
|
85
|
+
return promise;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 多次同步动作仅触发一次副作用
|
|
89
|
+
* @param action
|
|
90
|
+
* @param effect
|
|
91
|
+
*/
|
|
92
|
+
export function createBatchAction(action, effect) {
|
|
93
|
+
let prevPending;
|
|
94
|
+
return function (...args) {
|
|
95
|
+
try {
|
|
96
|
+
action.apply(this, args);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
prevPending?.abort();
|
|
100
|
+
prevPending = nextTick(effect);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|