@pixel-starry/hook-store 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 +90 -0
- package/dist/deepGet.d.ts +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +227 -0
- package/dist/shallowEqual.d.ts +6 -0
- package/dist/store.d.ts +58 -0
- package/package.json +32 -0
- package/src/deepGet.ts +16 -0
- package/src/index.tsx +1 -0
- package/src/shallowEqual.ts +52 -0
- package/src/store.tsx +169 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Bytedance Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# HookStore
|
|
2
|
+
HookStore 是一个轻量级的 React 状态管理 hooks 库,支持精细化更新。
|
|
3
|
+
|
|
4
|
+
## store 定义
|
|
5
|
+
现在我们来编写一个计数器 store
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { BaseStore } from '@pixel-starry/hook-store';
|
|
9
|
+
|
|
10
|
+
class CountStore extends BaseStore {
|
|
11
|
+
count = 0;
|
|
12
|
+
count2 = 0;
|
|
13
|
+
|
|
14
|
+
add(num: number) {
|
|
15
|
+
this.setState({ count: num });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
reset() {
|
|
19
|
+
this.setState({ count: 0 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
times(num: number) {
|
|
23
|
+
this.setState({ count: this.count * num });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 使用 store
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { StoreProvider, useStore } from '@pixel-starry/hook-store';
|
|
33
|
+
const Child = () => {
|
|
34
|
+
const store = useStore(CountStore);
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<div>{store.count}</div>
|
|
38
|
+
<button onClick={() => store.add(1)}>加1</button>
|
|
39
|
+
<button onClick={() => store.times(2)}>乘2</button>
|
|
40
|
+
<button onClick={() => store.reset()}>复位</button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const Root = () => {
|
|
46
|
+
return (
|
|
47
|
+
<StoreProvider store={CountStore}>
|
|
48
|
+
<Child />
|
|
49
|
+
</StoreProvider>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
* BaseStore
|
|
58
|
+
|
|
59
|
+
store的基类,提供了 setState 来更新数据
|
|
60
|
+
|
|
61
|
+
* StoreProvider
|
|
62
|
+
|
|
63
|
+
Context Provider 组件,需要把当前的 store 传递给 Provider 组件
|
|
64
|
+
|
|
65
|
+
* withStore
|
|
66
|
+
|
|
67
|
+
StoreProvider 另一种用法, 用法 withStore(Child, CountStore);
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
const Root = withStore(Child, CountStore);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
* useStore
|
|
74
|
+
|
|
75
|
+
获取 store 实例,第一个参数是 BaseStore,第二个参数是更新依赖项。
|
|
76
|
+
|
|
77
|
+
如:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
// 调用 setState 组件就会更新
|
|
81
|
+
const store = useStore(CountStore);
|
|
82
|
+
|
|
83
|
+
// 调用 setState 组件不会更新
|
|
84
|
+
const store = useStore(CountStore, []);
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
// 调用 setState 后, count值变化组件会更新,否则不会更新
|
|
88
|
+
const store = useStore(CountStore, ['count']);
|
|
89
|
+
|
|
90
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function deepGet(obj: any, path: string[] | string): any;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type InstanceType<T> = T extends abstract new (...args: any) => infer R ? R : never;
|
|
4
|
+
declare class BaseStore {
|
|
5
|
+
_update?: () => void;
|
|
6
|
+
static _current?: BaseStore;
|
|
7
|
+
static _isBaseStore: boolean;
|
|
8
|
+
static clear(): void;
|
|
9
|
+
StoreContext: React.Context<{
|
|
10
|
+
store: BaseStore;
|
|
11
|
+
fns: Set<() => void>;
|
|
12
|
+
}>;
|
|
13
|
+
setState<K extends keyof this>(state?: {
|
|
14
|
+
[k in K]: this[k];
|
|
15
|
+
}): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param store 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
*
|
|
23
|
+
* <StoreProvider store={UserStore}></StoreProvider>
|
|
24
|
+
*
|
|
25
|
+
* 或者
|
|
26
|
+
*
|
|
27
|
+
* const userStore = new UserStore();
|
|
28
|
+
*
|
|
29
|
+
* <StoreProvider store={userStore}></StoreProvider>
|
|
30
|
+
*
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
declare function StoreProvider<T extends BaseStore | Function>({ store, children, }: {
|
|
34
|
+
store: T | (() => T);
|
|
35
|
+
children: React.ReactNode;
|
|
36
|
+
}): React.JSX.Element;
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param baseStore 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
40
|
+
* @example
|
|
41
|
+
*
|
|
42
|
+
* useStore(UserStore)
|
|
43
|
+
*
|
|
44
|
+
* 或者
|
|
45
|
+
*
|
|
46
|
+
* useStore(() => new UserStore())
|
|
47
|
+
*
|
|
48
|
+
* 或者
|
|
49
|
+
*
|
|
50
|
+
* const userStore = new UserStore();
|
|
51
|
+
*
|
|
52
|
+
* useStore(userStore)
|
|
53
|
+
*
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
declare function useStore<T extends BaseStore | Function>(baseStore: T | (() => T), deps?: Array<string | string[]>): Exclude<T, Function> | InstanceType<T>;
|
|
57
|
+
type ClassItSelf<T> = new () => T;
|
|
58
|
+
declare function withStore<P, S extends ClassItSelf<BaseStore> | BaseStore | Function>(Cpt: React.FC<P>, store: S): (props: P) => React.JSX.Element;
|
|
59
|
+
|
|
60
|
+
export { BaseStore, InstanceType, StoreProvider, useStore, withStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, { useState, useContext, useRef, useLayoutEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/******************************************************************************
|
|
4
|
+
Copyright (c) Microsoft Corporation.
|
|
5
|
+
|
|
6
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
7
|
+
purpose with or without fee is hereby granted.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
***************************************************************************** */
|
|
17
|
+
|
|
18
|
+
var __assign = function() {
|
|
19
|
+
__assign = Object.assign || function __assign(t) {
|
|
20
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
21
|
+
s = arguments[i];
|
|
22
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
};
|
|
26
|
+
return __assign.apply(this, arguments);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
30
|
+
var e = new Error(message);
|
|
31
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
35
|
+
/**
|
|
36
|
+
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
|
37
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
38
|
+
*/
|
|
39
|
+
function is(x, y) {
|
|
40
|
+
// SameValue algorithm
|
|
41
|
+
if (x === y) { // Steps 1-5, 7-10
|
|
42
|
+
// Steps 6.b-6.e: +0 != -0
|
|
43
|
+
// Added the nonzero y check to make Flow happy, but it is redundant
|
|
44
|
+
return x !== 0 || y !== 0 || 1 / x === 1 / y;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Step 6.a: NaN == NaN
|
|
48
|
+
return x !== x && y !== y;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Performs equality by iterating through keys on an object and returning false
|
|
53
|
+
* when any key has values which are not strictly equal between the arguments.
|
|
54
|
+
* Returns true when the values of all keys are strictly equal.
|
|
55
|
+
*/
|
|
56
|
+
function shallowEqual(objA, objB) {
|
|
57
|
+
if (is(objA, objB)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (typeof objA !== 'object' || objA === null ||
|
|
61
|
+
typeof objB !== 'object' || objB === null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
var keysA = Object.keys(objA);
|
|
65
|
+
var keysB = Object.keys(objB);
|
|
66
|
+
if (keysA.length !== keysB.length) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// Test for A's keys different from B.
|
|
70
|
+
for (var i = 0; i < keysA.length; i++) {
|
|
71
|
+
if (!hasOwnProperty.call(objB, keysA[i]) ||
|
|
72
|
+
!is(objA[keysA[i]], objB[keysA[i]])) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function deepGet(obj, path) {
|
|
80
|
+
if (obj == null) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
if (typeof path === "string") {
|
|
84
|
+
return obj[path];
|
|
85
|
+
}
|
|
86
|
+
var length = path.length;
|
|
87
|
+
for (var i = 0; i < length; i++) {
|
|
88
|
+
if (obj == null)
|
|
89
|
+
return void 0;
|
|
90
|
+
obj = obj[path[i]];
|
|
91
|
+
}
|
|
92
|
+
return length ? obj : void 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var BaseStore = /** @class */ (function () {
|
|
96
|
+
function BaseStore() {
|
|
97
|
+
this.StoreContext = React.createContext({
|
|
98
|
+
store: this,
|
|
99
|
+
fns: new Set(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// 清空的时机不好确认,目前需要手动清空
|
|
103
|
+
BaseStore.clear = function () {
|
|
104
|
+
this._current = undefined;
|
|
105
|
+
};
|
|
106
|
+
BaseStore.prototype.setState = function (state) {
|
|
107
|
+
var _a;
|
|
108
|
+
if (state) {
|
|
109
|
+
Object.assign(this, state);
|
|
110
|
+
}
|
|
111
|
+
(_a = this._update) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
112
|
+
};
|
|
113
|
+
BaseStore._isBaseStore = true;
|
|
114
|
+
return BaseStore;
|
|
115
|
+
}());
|
|
116
|
+
/**
|
|
117
|
+
*
|
|
118
|
+
* @param store 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
*
|
|
122
|
+
* <StoreProvider store={UserStore}></StoreProvider>
|
|
123
|
+
*
|
|
124
|
+
* 或者
|
|
125
|
+
*
|
|
126
|
+
* const userStore = new UserStore();
|
|
127
|
+
*
|
|
128
|
+
* <StoreProvider store={userStore}></StoreProvider>
|
|
129
|
+
*
|
|
130
|
+
* @returns
|
|
131
|
+
*/
|
|
132
|
+
function StoreProvider(_a) {
|
|
133
|
+
var store = _a.store, children = _a.children;
|
|
134
|
+
// store 只会在初始化时执行一次,后面新生成的store是无效的
|
|
135
|
+
var finalStore = useState(function () {
|
|
136
|
+
var _store;
|
|
137
|
+
if (store instanceof BaseStore) {
|
|
138
|
+
_store = store;
|
|
139
|
+
}
|
|
140
|
+
else if (store._isBaseStore) {
|
|
141
|
+
_store = new store();
|
|
142
|
+
_store.constructor._current = _store;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
_store = store();
|
|
146
|
+
}
|
|
147
|
+
return _store;
|
|
148
|
+
})[0];
|
|
149
|
+
var value = useState(function () { return ({
|
|
150
|
+
store: finalStore,
|
|
151
|
+
fns: new Set(),
|
|
152
|
+
}); })[0];
|
|
153
|
+
var StoreContext = finalStore.StoreContext;
|
|
154
|
+
finalStore._update = function () {
|
|
155
|
+
value.fns.forEach(function (fn) { return fn(); });
|
|
156
|
+
};
|
|
157
|
+
return (React.createElement(StoreContext.Provider, { value: value }, children));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
*
|
|
161
|
+
* @param baseStore 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
162
|
+
* @example
|
|
163
|
+
*
|
|
164
|
+
* useStore(UserStore)
|
|
165
|
+
*
|
|
166
|
+
* 或者
|
|
167
|
+
*
|
|
168
|
+
* useStore(() => new UserStore())
|
|
169
|
+
*
|
|
170
|
+
* 或者
|
|
171
|
+
*
|
|
172
|
+
* const userStore = new UserStore();
|
|
173
|
+
*
|
|
174
|
+
* useStore(userStore)
|
|
175
|
+
*
|
|
176
|
+
* @returns
|
|
177
|
+
*/
|
|
178
|
+
function useStore(baseStore, deps) {
|
|
179
|
+
// store 只会在初始化时执行一次,后面新生成的store是无效的
|
|
180
|
+
var tmpStore = useState(function () {
|
|
181
|
+
var _a;
|
|
182
|
+
var _store;
|
|
183
|
+
if (baseStore instanceof BaseStore) {
|
|
184
|
+
_store = baseStore;
|
|
185
|
+
}
|
|
186
|
+
else if (baseStore._isBaseStore) {
|
|
187
|
+
var BS = baseStore;
|
|
188
|
+
_store = (_a = BS._current) !== null && _a !== void 0 ? _a : new BS();
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
_store = baseStore();
|
|
192
|
+
}
|
|
193
|
+
return _store;
|
|
194
|
+
})[0];
|
|
195
|
+
var _a = useContext(tmpStore.StoreContext), store = _a.store, fns = _a.fns;
|
|
196
|
+
var getDepVals = function () { return deps === null || deps === void 0 ? void 0 : deps.map(function (key) { return deepGet(store, key); }); };
|
|
197
|
+
var ref = useRef();
|
|
198
|
+
var _b = useState(getDepVals), state = _b[0], dispatch = _b[1];
|
|
199
|
+
ref.current = state;
|
|
200
|
+
useLayoutEffect(function () {
|
|
201
|
+
var fn = function () {
|
|
202
|
+
if (deps) {
|
|
203
|
+
var newVal = getDepVals();
|
|
204
|
+
var old = ref.current;
|
|
205
|
+
if (!shallowEqual(newVal, old)) {
|
|
206
|
+
dispatch(newVal);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
dispatch([]);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
fns.add(fn);
|
|
214
|
+
return function () {
|
|
215
|
+
fns.delete(fn);
|
|
216
|
+
};
|
|
217
|
+
}, deps !== null && deps !== void 0 ? deps : []);
|
|
218
|
+
return store;
|
|
219
|
+
}
|
|
220
|
+
function withStore(Cpt, store) {
|
|
221
|
+
return function (props) {
|
|
222
|
+
return (React.createElement(StoreProvider, { store: store },
|
|
223
|
+
React.createElement(Cpt, __assign({}, props))));
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { BaseStore, StoreProvider, useStore, withStore };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs equality by iterating through keys on an object and returning false
|
|
3
|
+
* when any key has values which are not strictly equal between the arguments.
|
|
4
|
+
* Returns true when the values of all keys are strictly equal.
|
|
5
|
+
*/
|
|
6
|
+
export declare function shallowEqual(objA: any, objB: any): boolean;
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type InstanceType<T> = T extends abstract new (...args: any) => infer R ? R : never;
|
|
3
|
+
export declare class BaseStore {
|
|
4
|
+
_update?: () => void;
|
|
5
|
+
static _current?: BaseStore;
|
|
6
|
+
static _isBaseStore: boolean;
|
|
7
|
+
static clear(): void;
|
|
8
|
+
StoreContext: React.Context<{
|
|
9
|
+
store: BaseStore;
|
|
10
|
+
fns: Set<() => void>;
|
|
11
|
+
}>;
|
|
12
|
+
setState<K extends keyof this>(state?: {
|
|
13
|
+
[k in K]: this[k];
|
|
14
|
+
}): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param store 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
*
|
|
22
|
+
* <StoreProvider store={UserStore}></StoreProvider>
|
|
23
|
+
*
|
|
24
|
+
* 或者
|
|
25
|
+
*
|
|
26
|
+
* const userStore = new UserStore();
|
|
27
|
+
*
|
|
28
|
+
* <StoreProvider store={userStore}></StoreProvider>
|
|
29
|
+
*
|
|
30
|
+
* @returns
|
|
31
|
+
*/
|
|
32
|
+
export declare function StoreProvider<T extends BaseStore | Function>({ store, children, }: {
|
|
33
|
+
store: T | (() => T);
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
}): React.JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param baseStore 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
39
|
+
* @example
|
|
40
|
+
*
|
|
41
|
+
* useStore(UserStore)
|
|
42
|
+
*
|
|
43
|
+
* 或者
|
|
44
|
+
*
|
|
45
|
+
* useStore(() => new UserStore())
|
|
46
|
+
*
|
|
47
|
+
* 或者
|
|
48
|
+
*
|
|
49
|
+
* const userStore = new UserStore();
|
|
50
|
+
*
|
|
51
|
+
* useStore(userStore)
|
|
52
|
+
*
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
export declare function useStore<T extends BaseStore | Function>(baseStore: T | (() => T), deps?: Array<string | string[]>): Exclude<T, Function> | InstanceType<T>;
|
|
56
|
+
type ClassItSelf<T> = new () => T;
|
|
57
|
+
export declare function withStore<P, S extends ClassItSelf<BaseStore> | BaseStore | Function>(Cpt: React.FC<P>, store: S): (props: P) => React.JSX.Element;
|
|
58
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pixel-starry/hook-store",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A light data management tool for react framework.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"typings": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "cd example && yarn dev",
|
|
9
|
+
"build": "rollup --config"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git@github.com:Pixel-Starry/hook-store.git"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "wanglei8381",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@rollup/plugin-typescript": "^8.3.0",
|
|
19
|
+
"rollup-plugin-dts": "^4.1.0",
|
|
20
|
+
"@types/react": "^17.0.39",
|
|
21
|
+
"react": "^17.0.2",
|
|
22
|
+
"rollup": "^2.67.3",
|
|
23
|
+
"tslib": "^2.3.1",
|
|
24
|
+
"typescript": "^4.5.5"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/deepGet.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function deepGet(obj: any, path: string[] | string) {
|
|
2
|
+
if (obj == null) {
|
|
3
|
+
return void 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (typeof path === "string") {
|
|
7
|
+
return obj[path];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let length = path.length;
|
|
11
|
+
for (let i = 0; i < length; i++) {
|
|
12
|
+
if (obj == null) return void 0;
|
|
13
|
+
obj = obj[path[i]];
|
|
14
|
+
}
|
|
15
|
+
return length ? obj : void 0;
|
|
16
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './store';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
|
5
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
6
|
+
*/
|
|
7
|
+
function is(x: any, y: any): boolean {
|
|
8
|
+
// SameValue algorithm
|
|
9
|
+
if (x === y) { // Steps 1-5, 7-10
|
|
10
|
+
// Steps 6.b-6.e: +0 != -0
|
|
11
|
+
// Added the nonzero y check to make Flow happy, but it is redundant
|
|
12
|
+
return x !== 0 || y !== 0 || 1 / x === 1 / y;
|
|
13
|
+
} else {
|
|
14
|
+
// Step 6.a: NaN == NaN
|
|
15
|
+
return x !== x && y !== y;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Performs equality by iterating through keys on an object and returning false
|
|
21
|
+
* when any key has values which are not strictly equal between the arguments.
|
|
22
|
+
* Returns true when the values of all keys are strictly equal.
|
|
23
|
+
*/
|
|
24
|
+
export function shallowEqual(objA: any, objB: any): boolean {
|
|
25
|
+
if (is(objA, objB)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof objA !== 'object' || objA === null ||
|
|
30
|
+
typeof objB !== 'object' || objB === null) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const keysA = Object.keys(objA);
|
|
35
|
+
const keysB = Object.keys(objB);
|
|
36
|
+
|
|
37
|
+
if (keysA.length !== keysB.length) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Test for A's keys different from B.
|
|
42
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
43
|
+
if (
|
|
44
|
+
!hasOwnProperty.call(objB, keysA[i]) ||
|
|
45
|
+
!is(objA[keysA[i]], objB[keysA[i]])
|
|
46
|
+
) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
package/src/store.tsx
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React, { useContext, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { shallowEqual } from "./shallowEqual";
|
|
3
|
+
import deepGet from "./deepGet";
|
|
4
|
+
|
|
5
|
+
export type InstanceType<T> = T extends abstract new (...args: any) => infer R
|
|
6
|
+
? R
|
|
7
|
+
: never;
|
|
8
|
+
|
|
9
|
+
export class BaseStore {
|
|
10
|
+
_update?: () => void;
|
|
11
|
+
|
|
12
|
+
static _current?: BaseStore;
|
|
13
|
+
|
|
14
|
+
static _isBaseStore = true;
|
|
15
|
+
|
|
16
|
+
// 清空的时机不好确认,目前需要手动清空
|
|
17
|
+
static clear() {
|
|
18
|
+
this._current = undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
StoreContext = React.createContext({
|
|
22
|
+
store: this as BaseStore,
|
|
23
|
+
fns: new Set<() => void>(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
setState<K extends keyof this>(state?: { [k in K]: this[k] }) {
|
|
27
|
+
if (state) {
|
|
28
|
+
Object.assign(this, state);
|
|
29
|
+
}
|
|
30
|
+
this._update?.();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type BaseStoreCls = typeof BaseStore;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param store 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
*
|
|
42
|
+
* <StoreProvider store={UserStore}></StoreProvider>
|
|
43
|
+
*
|
|
44
|
+
* 或者
|
|
45
|
+
*
|
|
46
|
+
* const userStore = new UserStore();
|
|
47
|
+
*
|
|
48
|
+
* <StoreProvider store={userStore}></StoreProvider>
|
|
49
|
+
*
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
export function StoreProvider<T extends BaseStore | Function>({
|
|
53
|
+
store,
|
|
54
|
+
children,
|
|
55
|
+
}: {
|
|
56
|
+
store: T | (() => T);
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
}) {
|
|
59
|
+
// store 只会在初始化时执行一次,后面新生成的store是无效的
|
|
60
|
+
const [finalStore] = useState<BaseStore>(() => {
|
|
61
|
+
let _store: BaseStore;
|
|
62
|
+
if (store instanceof BaseStore) {
|
|
63
|
+
_store = store;
|
|
64
|
+
} else if ((store as any)._isBaseStore) {
|
|
65
|
+
_store = new (store as any)();
|
|
66
|
+
(_store.constructor as BaseStoreCls)._current = _store;
|
|
67
|
+
} else {
|
|
68
|
+
_store = store();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return _store;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const [value] = useState(() => ({
|
|
75
|
+
store: finalStore,
|
|
76
|
+
fns: new Set<() => void>(),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const { StoreContext } = finalStore;
|
|
80
|
+
|
|
81
|
+
finalStore._update = () => {
|
|
82
|
+
value.fns.forEach((fn) => fn());
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<StoreContext.Provider value={value}>{children}</StoreContext.Provider>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
*
|
|
92
|
+
* @param baseStore 数据源,不追踪其变化,在首次初始化就需要赋值
|
|
93
|
+
* @example
|
|
94
|
+
*
|
|
95
|
+
* useStore(UserStore)
|
|
96
|
+
*
|
|
97
|
+
* 或者
|
|
98
|
+
*
|
|
99
|
+
* useStore(() => new UserStore())
|
|
100
|
+
*
|
|
101
|
+
* 或者
|
|
102
|
+
*
|
|
103
|
+
* const userStore = new UserStore();
|
|
104
|
+
*
|
|
105
|
+
* useStore(userStore)
|
|
106
|
+
*
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
export function useStore<T extends BaseStore | Function>(
|
|
110
|
+
baseStore: T | (() => T),
|
|
111
|
+
deps?: Array<string | string[]>
|
|
112
|
+
) {
|
|
113
|
+
// store 只会在初始化时执行一次,后面新生成的store是无效的
|
|
114
|
+
const [tmpStore] = useState(() => {
|
|
115
|
+
let _store: BaseStore;
|
|
116
|
+
if (baseStore instanceof BaseStore) {
|
|
117
|
+
_store = baseStore;
|
|
118
|
+
} else if ((baseStore as any)._isBaseStore) {
|
|
119
|
+
const BS = baseStore as BaseStoreCls;
|
|
120
|
+
_store = BS._current ?? new BS();
|
|
121
|
+
} else {
|
|
122
|
+
_store = baseStore();
|
|
123
|
+
}
|
|
124
|
+
return _store;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const { store, fns } = useContext(tmpStore.StoreContext);
|
|
128
|
+
|
|
129
|
+
const getDepVals = () => deps?.map((key) => deepGet(store, key));
|
|
130
|
+
|
|
131
|
+
const ref = useRef<any>();
|
|
132
|
+
const [state, dispatch] = useState(getDepVals);
|
|
133
|
+
ref.current = state;
|
|
134
|
+
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const fn = () => {
|
|
137
|
+
if (deps) {
|
|
138
|
+
const newVal = getDepVals();
|
|
139
|
+
const old = ref.current;
|
|
140
|
+
if (!shallowEqual(newVal, old)) {
|
|
141
|
+
dispatch(newVal);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
dispatch([]);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
fns.add(fn);
|
|
148
|
+
return () => {
|
|
149
|
+
fns.delete(fn);
|
|
150
|
+
};
|
|
151
|
+
}, deps ?? []);
|
|
152
|
+
|
|
153
|
+
return store as Exclude<T, Function> | InstanceType<T>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type ClassItSelf<T> = new () => T;
|
|
157
|
+
|
|
158
|
+
export function withStore<
|
|
159
|
+
P,
|
|
160
|
+
S extends ClassItSelf<BaseStore> | BaseStore | Function
|
|
161
|
+
>(Cpt: React.FC<P>, store: S) {
|
|
162
|
+
return (props: P) => {
|
|
163
|
+
return (
|
|
164
|
+
<StoreProvider store={store}>
|
|
165
|
+
<Cpt {...(props as any)} />
|
|
166
|
+
</StoreProvider>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
}
|