@international-iot-association/rct-state 3.0.0-rc.1
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/README.md +87 -0
- package/dist/index.cjs +554 -0
- package/dist/index.d.cts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +522 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @international-iot-association/rct-state
|
|
2
|
+
|
|
3
|
+
Legend-State 风格的 RxJS + Immutable 响应式状态库,供工位插件的 React UI 使用。
|
|
4
|
+
属于**组 A · 协议锁步组**(与 `plugin-contracts`/`plugin-sdk`/`api-bridge` 一起随 `agentApi` major 锁步发布)。
|
|
5
|
+
|
|
6
|
+
## 安装
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm i @international-iot-association/rct-state
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
Basic usage for pure JavaScript:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// file: userData.ts
|
|
18
|
+
|
|
19
|
+
import { observable } from 'rct-state';
|
|
20
|
+
|
|
21
|
+
// define type for state
|
|
22
|
+
interface User {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const user: User = {
|
|
28
|
+
id: 1,
|
|
29
|
+
name: 'jojo',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// create store
|
|
33
|
+
export const user$ = observable(user);
|
|
34
|
+
|
|
35
|
+
// get user id
|
|
36
|
+
const userId = user$.id.get();
|
|
37
|
+
|
|
38
|
+
// set user id
|
|
39
|
+
user$.id.set(99);
|
|
40
|
+
|
|
41
|
+
// watch user id change
|
|
42
|
+
user$.observe((state) => {
|
|
43
|
+
return state.id
|
|
44
|
+
}, (id) => {
|
|
45
|
+
console.log('user id changed:', id)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Basic usage for React Native or React component:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import React from 'react';
|
|
54
|
+
import { View, Text } from 'react-native'
|
|
55
|
+
import { user$ } from './userData'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export function UserPanel() {
|
|
59
|
+
const { id, name } = user$.use()
|
|
60
|
+
const userId = user$.id.use()
|
|
61
|
+
const username = user$.useSelector((state) => state.id)
|
|
62
|
+
|
|
63
|
+
// watch value change
|
|
64
|
+
user$.useObserve((state) => state.name, username => {
|
|
65
|
+
console.log(`user name changed: ${username}`)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<View>
|
|
70
|
+
<Text>ID: { id }</Text>
|
|
71
|
+
<Text>Name: { name }</Text>
|
|
72
|
+
</View>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Contributing
|
|
78
|
+
|
|
79
|
+
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
createDeepProxy: () => createDeepProxy,
|
|
34
|
+
observable: () => observable
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/proxy/wrapper.ts
|
|
39
|
+
var Wrapper = class {
|
|
40
|
+
value;
|
|
41
|
+
keyPath = [];
|
|
42
|
+
originalObject;
|
|
43
|
+
onSet;
|
|
44
|
+
onUse;
|
|
45
|
+
proxy = new Proxy(this, {
|
|
46
|
+
get(target, property) {
|
|
47
|
+
if (property in target) {
|
|
48
|
+
return target[property];
|
|
49
|
+
} else {
|
|
50
|
+
return target.value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
constructor(value, keyPath = [], originalObject, onSet, onUse) {
|
|
55
|
+
this.value = value;
|
|
56
|
+
this.keyPath = keyPath;
|
|
57
|
+
this.originalObject = originalObject;
|
|
58
|
+
this.onSet = onSet;
|
|
59
|
+
this.onUse = onUse;
|
|
60
|
+
return this.proxy;
|
|
61
|
+
}
|
|
62
|
+
valueOf() {
|
|
63
|
+
return this.value;
|
|
64
|
+
}
|
|
65
|
+
get() {
|
|
66
|
+
return this.peek();
|
|
67
|
+
}
|
|
68
|
+
peek() {
|
|
69
|
+
const value = this.originalObject().getIn(this.keyPath);
|
|
70
|
+
if (typeof value === "function") {
|
|
71
|
+
return value.bind(this.originalObject)();
|
|
72
|
+
} else {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
get __keyPath() {
|
|
77
|
+
return this.keyPath;
|
|
78
|
+
}
|
|
79
|
+
get __self() {
|
|
80
|
+
return {
|
|
81
|
+
value: this.value,
|
|
82
|
+
keyPath: this.keyPath
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
toString() {
|
|
86
|
+
return this.keyPath.join(".");
|
|
87
|
+
}
|
|
88
|
+
toJSON() {
|
|
89
|
+
return JSON.stringify(this.keyPath);
|
|
90
|
+
}
|
|
91
|
+
set(value) {
|
|
92
|
+
this.onSet(value, this.keyPath);
|
|
93
|
+
}
|
|
94
|
+
use() {
|
|
95
|
+
return this.onUse(this.keyPath);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/proxy/wrapObject.ts
|
|
100
|
+
var import_immutable = __toESM(require("immutable"), 1);
|
|
101
|
+
function wrapObject(obj, keyPath = [], originalObject, onSet, onUse, _2) {
|
|
102
|
+
return new Proxy(obj, {
|
|
103
|
+
get(target, prop, receiver) {
|
|
104
|
+
switch (prop) {
|
|
105
|
+
case "get":
|
|
106
|
+
return () => originalObject().getIn(keyPath);
|
|
107
|
+
case "peek":
|
|
108
|
+
return () => originalObject().getIn(keyPath);
|
|
109
|
+
case "set":
|
|
110
|
+
return (value) => {
|
|
111
|
+
onSet && onSet(value, keyPath);
|
|
112
|
+
};
|
|
113
|
+
case "use":
|
|
114
|
+
return () => {
|
|
115
|
+
return onUse(keyPath);
|
|
116
|
+
};
|
|
117
|
+
case "__keyPath":
|
|
118
|
+
return keyPath;
|
|
119
|
+
case "isImmutableList":
|
|
120
|
+
return import_immutable.default.isList(target);
|
|
121
|
+
case "isImmutableMap":
|
|
122
|
+
return import_immutable.default.isMap(target);
|
|
123
|
+
case "__self":
|
|
124
|
+
return target;
|
|
125
|
+
// list functions
|
|
126
|
+
case "delete":
|
|
127
|
+
case "remove":
|
|
128
|
+
return (value) => {
|
|
129
|
+
if (import_immutable.default.isList(originalObject().getIn(keyPath))) {
|
|
130
|
+
originalObject().getIn(keyPath).delete(value);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
case "push":
|
|
134
|
+
return (...values) => {
|
|
135
|
+
const targetList = originalObject().getIn(keyPath);
|
|
136
|
+
if (!import_immutable.default.isList(targetList) && !Array.isArray(targetList)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
onSet && onSet([...targetList, ...values], keyPath);
|
|
140
|
+
};
|
|
141
|
+
// default
|
|
142
|
+
default:
|
|
143
|
+
return Reflect.get(target, prop, receiver);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/proxy/index.ts
|
|
150
|
+
function createDeepProxy(target, path = [], originalObject, onSet, onUse, notifyChange) {
|
|
151
|
+
return new Proxy(target, {
|
|
152
|
+
get(_target, property) {
|
|
153
|
+
if (property === "__self") {
|
|
154
|
+
return _target;
|
|
155
|
+
}
|
|
156
|
+
const keyPath = path.concat([property.toString()]);
|
|
157
|
+
const realValue = originalObject().getIn(keyPath);
|
|
158
|
+
if (typeof realValue === "object" && realValue !== null) {
|
|
159
|
+
return wrapObject(
|
|
160
|
+
// @ts-ignore
|
|
161
|
+
createDeepProxy(
|
|
162
|
+
realValue,
|
|
163
|
+
path.concat([property.toString()]),
|
|
164
|
+
originalObject,
|
|
165
|
+
onSet,
|
|
166
|
+
onUse
|
|
167
|
+
),
|
|
168
|
+
keyPath,
|
|
169
|
+
originalObject,
|
|
170
|
+
onSet,
|
|
171
|
+
onUse,
|
|
172
|
+
notifyChange
|
|
173
|
+
// realValue
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return new Wrapper(realValue, keyPath, originalObject, onSet, onUse);
|
|
177
|
+
},
|
|
178
|
+
set(_target, property, value, receiver) {
|
|
179
|
+
return Reflect.set(_target, property, value, receiver);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/manager.ts
|
|
185
|
+
var import_rxjs2 = require("rxjs");
|
|
186
|
+
|
|
187
|
+
// src/hooks/use.ts
|
|
188
|
+
var import_react2 = require("react");
|
|
189
|
+
var import_react_fast_compare2 = __toESM(require("react-fast-compare"), 1);
|
|
190
|
+
|
|
191
|
+
// src/utils/subscribe.ts
|
|
192
|
+
var import_react_fast_compare = __toESM(require("react-fast-compare"), 1);
|
|
193
|
+
var import_rxjs = require("rxjs");
|
|
194
|
+
var import_lodash = __toESM(require("lodash"), 1);
|
|
195
|
+
function generateSubForSpecificChange(config) {
|
|
196
|
+
const defaultValue = config.subject.value ? config.subject.value : config.defaultData ? config.filter(config.defaultData) : void 0;
|
|
197
|
+
const defaultFilteredData = defaultValue !== void 0 ? config.filter(defaultValue) : void 0;
|
|
198
|
+
const sub = config.subject.pipe(
|
|
199
|
+
(0, import_rxjs.startWith)(void 0),
|
|
200
|
+
(0, import_rxjs.map)((data) => {
|
|
201
|
+
if (data === void 0) {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
return import_lodash.default.cloneDeep(config.filter(data));
|
|
205
|
+
}),
|
|
206
|
+
(0, import_rxjs.pairwise)(),
|
|
207
|
+
(0, import_rxjs.filter)(([prev, next]) => {
|
|
208
|
+
return !(0, import_react_fast_compare.default)(prev, next);
|
|
209
|
+
}),
|
|
210
|
+
(0, import_rxjs.defaultIfEmpty)([defaultFilteredData, defaultFilteredData])
|
|
211
|
+
);
|
|
212
|
+
return sub;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/hooks/useState.ts
|
|
216
|
+
var import_react = require("react");
|
|
217
|
+
var import_lodash2 = require("lodash");
|
|
218
|
+
|
|
219
|
+
// src/utils/isSSR.ts
|
|
220
|
+
var isBrowser = typeof window !== "undefined";
|
|
221
|
+
var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
222
|
+
var isSSR = !(isBrowser || isReactNative);
|
|
223
|
+
|
|
224
|
+
// src/hooks/useState.ts
|
|
225
|
+
function useStateForClient(initialState) {
|
|
226
|
+
const stateValue = (0, import_react.useRef)(initialState || {});
|
|
227
|
+
const [state, _setState] = (0, import_react.useState)(initialState);
|
|
228
|
+
const setState = (value) => {
|
|
229
|
+
const valueIsFunction = typeof value === "function";
|
|
230
|
+
const targetValue = valueIsFunction ? value(stateValue.current) : value;
|
|
231
|
+
if (!(0, import_lodash2.isEqual)(targetValue, stateValue.current)) {
|
|
232
|
+
stateValue.current = targetValue;
|
|
233
|
+
_setState(targetValue);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
return [state, setState];
|
|
237
|
+
}
|
|
238
|
+
var useState = isSSR ? import_react.useState : useStateForClient;
|
|
239
|
+
|
|
240
|
+
// src/hooks/use.ts
|
|
241
|
+
function useFunc({ keyPath, subSource, getState }) {
|
|
242
|
+
const valueRef = (0, import_react2.useRef)(getState().getIn(keyPath));
|
|
243
|
+
const [value, _setValue] = useState(valueRef.current);
|
|
244
|
+
const setValue = (0, import_react2.useCallback)((nextValue) => {
|
|
245
|
+
if (!(0, import_react_fast_compare2.default)(valueRef.current, nextValue)) {
|
|
246
|
+
valueRef.current = nextValue;
|
|
247
|
+
_setValue(nextValue);
|
|
248
|
+
}
|
|
249
|
+
}, []);
|
|
250
|
+
(0, import_react2.useEffect)(() => {
|
|
251
|
+
const sub = generateSubForSpecificChange({
|
|
252
|
+
subject: subSource,
|
|
253
|
+
filter: () => {
|
|
254
|
+
return getState().getIn(keyPath);
|
|
255
|
+
},
|
|
256
|
+
mark: "useFunc"
|
|
257
|
+
}).subscribe(([_2, next]) => {
|
|
258
|
+
setValue(next);
|
|
259
|
+
});
|
|
260
|
+
return () => {
|
|
261
|
+
if (sub && sub.unsubscribe) {
|
|
262
|
+
sub.unsubscribe();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}, [keyPath, subSource, getState]);
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/hooks/useObserve.ts
|
|
270
|
+
var import_react3 = require("react");
|
|
271
|
+
|
|
272
|
+
// src/proxy/immutableGetter.ts
|
|
273
|
+
var import_immutable2 = require("immutable");
|
|
274
|
+
function createProxiedList(list) {
|
|
275
|
+
return new Proxy(list, {
|
|
276
|
+
get(target, key) {
|
|
277
|
+
const numKey = Number(key);
|
|
278
|
+
if (key === "__self") {
|
|
279
|
+
return target;
|
|
280
|
+
}
|
|
281
|
+
if (key === "isImmutableList") {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (Number.isNaN(numKey)) {
|
|
285
|
+
return Reflect.get(target, key);
|
|
286
|
+
}
|
|
287
|
+
const value = target.get(numKey);
|
|
288
|
+
if (import_immutable2.Map.isMap(value)) {
|
|
289
|
+
return createProxiedMap(value);
|
|
290
|
+
} else if (import_immutable2.List.isList(value)) {
|
|
291
|
+
return createProxiedList(value);
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
294
|
+
},
|
|
295
|
+
set(target, key, value) {
|
|
296
|
+
const numKey = Number(key);
|
|
297
|
+
if (Number.isNaN(numKey)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
target.set(numKey, value);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function createProxiedMap(map2) {
|
|
306
|
+
return new Proxy(map2, {
|
|
307
|
+
get(target, key) {
|
|
308
|
+
const mapValue = target.get(key);
|
|
309
|
+
if (mapValue === void 0) {
|
|
310
|
+
return mapValue;
|
|
311
|
+
}
|
|
312
|
+
if (key === "__self") {
|
|
313
|
+
return target;
|
|
314
|
+
}
|
|
315
|
+
if (key === "isImmutableMap") {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (import_immutable2.Map.isMap(mapValue)) {
|
|
319
|
+
return createProxiedMap(
|
|
320
|
+
mapValue
|
|
321
|
+
);
|
|
322
|
+
} else if (import_immutable2.List.isList(mapValue)) {
|
|
323
|
+
return createProxiedList(
|
|
324
|
+
mapValue
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return mapValue;
|
|
328
|
+
},
|
|
329
|
+
set(target, key, value) {
|
|
330
|
+
target.set(key, value);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function wrapGetterForMap(data) {
|
|
336
|
+
return createProxiedMap(data);
|
|
337
|
+
}
|
|
338
|
+
function wrapAsImmutable(data) {
|
|
339
|
+
const mapData = (0, import_immutable2.Map)(data);
|
|
340
|
+
return {
|
|
341
|
+
getter: createProxiedMap(mapData),
|
|
342
|
+
mapData
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/hooks/useObserve.ts
|
|
347
|
+
function useObserve({
|
|
348
|
+
selectorFunction,
|
|
349
|
+
subSource,
|
|
350
|
+
onChangeFunction,
|
|
351
|
+
getState
|
|
352
|
+
}) {
|
|
353
|
+
(0, import_react3.useEffect)(() => {
|
|
354
|
+
const sub = generateSubForSpecificChange({
|
|
355
|
+
subject: subSource,
|
|
356
|
+
filter: () => {
|
|
357
|
+
return selectorFunction(wrapGetterForMap(getState()));
|
|
358
|
+
},
|
|
359
|
+
mark: "useObserve"
|
|
360
|
+
}).subscribe(([_2, next]) => {
|
|
361
|
+
onChangeFunction(next);
|
|
362
|
+
});
|
|
363
|
+
return () => {
|
|
364
|
+
if (sub && sub.unsubscribe) {
|
|
365
|
+
sub.unsubscribe();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}, [subSource, getState, onChangeFunction, selectorFunction]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/hooks/useSelector.ts
|
|
372
|
+
var import_react4 = require("react");
|
|
373
|
+
|
|
374
|
+
// src/utils/isImmutableObject.ts
|
|
375
|
+
var IMMUTABLE_ITERABLE = "@@__IMMUTABLE_ITERABLE__@@";
|
|
376
|
+
var IMMUTABLE_RECORD = "@@__IMMUTABLE_RECORD__@@";
|
|
377
|
+
var IMMUTABLE_LIST = "@@__IMMUTABLE_LIST__@@";
|
|
378
|
+
var IMMUTABLE_MAP = "@@__IMMUTABLE_MAP__@@";
|
|
379
|
+
function isImmutableObject(obj) {
|
|
380
|
+
if (typeof obj !== "object") {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
const proto = Object.getPrototypeOf(obj);
|
|
384
|
+
if (proto[IMMUTABLE_ITERABLE] || proto[IMMUTABLE_RECORD] || proto[IMMUTABLE_LIST] || proto[IMMUTABLE_MAP]) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/hooks/useSelector.ts
|
|
391
|
+
function useSelector({
|
|
392
|
+
selectorFunction,
|
|
393
|
+
subSource,
|
|
394
|
+
getState
|
|
395
|
+
}) {
|
|
396
|
+
const wrappedSelectorFunction = (0, import_react4.useCallback)(
|
|
397
|
+
(getJS = false) => () => {
|
|
398
|
+
const state = getState();
|
|
399
|
+
const res = selectorFunction(wrapGetterForMap(state));
|
|
400
|
+
if (isImmutableObject(res)) {
|
|
401
|
+
if (getJS) {
|
|
402
|
+
return state.toJS();
|
|
403
|
+
}
|
|
404
|
+
return state;
|
|
405
|
+
}
|
|
406
|
+
return res;
|
|
407
|
+
},
|
|
408
|
+
[selectorFunction, getState]
|
|
409
|
+
);
|
|
410
|
+
const [value, setValue] = useState(wrappedSelectorFunction(true)());
|
|
411
|
+
(0, import_react4.useEffect)(() => {
|
|
412
|
+
const sub = generateSubForSpecificChange({
|
|
413
|
+
subject: subSource,
|
|
414
|
+
filter: wrappedSelectorFunction(),
|
|
415
|
+
mark: "useSelector"
|
|
416
|
+
}).subscribe(([_2, next]) => {
|
|
417
|
+
if (isImmutableObject(next)) {
|
|
418
|
+
setValue(next.toJS());
|
|
419
|
+
} else {
|
|
420
|
+
setValue(next);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return () => {
|
|
424
|
+
if (sub && sub.unsubscribe) {
|
|
425
|
+
sub.unsubscribe();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}, [wrappedSelectorFunction, getState, subSource]);
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/manager.ts
|
|
433
|
+
var ObserverableManager = class {
|
|
434
|
+
state;
|
|
435
|
+
subject;
|
|
436
|
+
_state$;
|
|
437
|
+
batchProcessing = false;
|
|
438
|
+
stateGetter;
|
|
439
|
+
get state$() {
|
|
440
|
+
return new Proxy(this._state$, {
|
|
441
|
+
get: (target, property, receiver) => {
|
|
442
|
+
switch (property) {
|
|
443
|
+
case "useSelector":
|
|
444
|
+
return this.useSelector;
|
|
445
|
+
case "useObserve":
|
|
446
|
+
return this.useObserve;
|
|
447
|
+
case "batch":
|
|
448
|
+
return this.batch;
|
|
449
|
+
case "peek":
|
|
450
|
+
return this.peek;
|
|
451
|
+
case "observe":
|
|
452
|
+
return this.observe;
|
|
453
|
+
case "__immutable__":
|
|
454
|
+
return this.state;
|
|
455
|
+
default:
|
|
456
|
+
return Reflect.get(target, property, receiver);
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
set: () => {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
constructor(state) {
|
|
465
|
+
const wrappedState = wrapAsImmutable(state);
|
|
466
|
+
this.state = wrappedState.mapData;
|
|
467
|
+
this.stateGetter = wrappedState.getter;
|
|
468
|
+
this.subject = new import_rxjs2.BehaviorSubject(this.state);
|
|
469
|
+
this._state$ = createDeepProxy(
|
|
470
|
+
state,
|
|
471
|
+
[],
|
|
472
|
+
this.getCurrentState,
|
|
473
|
+
this.onSet,
|
|
474
|
+
this.onUse,
|
|
475
|
+
this.notifyChange
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* This method will be utilized by the 'get' method of the Proxy and the subscription logic within hooks,
|
|
480
|
+
* with the aim of obtaining the latest 'state' reference.
|
|
481
|
+
* @returns
|
|
482
|
+
*/
|
|
483
|
+
getCurrentState = () => {
|
|
484
|
+
return this.state;
|
|
485
|
+
};
|
|
486
|
+
onSet = (value, keyPath) => {
|
|
487
|
+
this.state = this.state.setIn(keyPath, value);
|
|
488
|
+
this.notifyChange();
|
|
489
|
+
};
|
|
490
|
+
notifyChange = () => {
|
|
491
|
+
if (this.batchProcessing) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
this.subject.next(this.state);
|
|
495
|
+
};
|
|
496
|
+
onUse = (keyPath) => {
|
|
497
|
+
return useFunc({
|
|
498
|
+
keyPath,
|
|
499
|
+
subSource: this.subject,
|
|
500
|
+
getState: this.getCurrentState
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
useSelector = (selector) => {
|
|
504
|
+
return useSelector({
|
|
505
|
+
selectorFunction: selector,
|
|
506
|
+
subSource: this.subject,
|
|
507
|
+
getState: this.getCurrentState
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
useObserve = (selector, onChange) => {
|
|
511
|
+
useObserve({
|
|
512
|
+
selectorFunction: selector,
|
|
513
|
+
subSource: this.subject,
|
|
514
|
+
onChangeFunction: onChange,
|
|
515
|
+
getState: this.getCurrentState
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
batch = (batchAction) => {
|
|
519
|
+
this.batchProcessing = true;
|
|
520
|
+
batchAction();
|
|
521
|
+
this.batchProcessing = false;
|
|
522
|
+
this.notifyChange();
|
|
523
|
+
};
|
|
524
|
+
peek = () => {
|
|
525
|
+
return this.state.toJS();
|
|
526
|
+
};
|
|
527
|
+
get = () => {
|
|
528
|
+
return this.peek();
|
|
529
|
+
};
|
|
530
|
+
observe = (selector, onChange) => {
|
|
531
|
+
const sub = generateSubForSpecificChange({
|
|
532
|
+
subject: this.subject,
|
|
533
|
+
filter: () => {
|
|
534
|
+
const res = selector(wrapGetterForMap(this.state));
|
|
535
|
+
return res;
|
|
536
|
+
}
|
|
537
|
+
}).subscribe(([_2, next]) => {
|
|
538
|
+
onChange(next);
|
|
539
|
+
});
|
|
540
|
+
return () => {
|
|
541
|
+
if (sub && sub.unsubscribe) {
|
|
542
|
+
sub.unsubscribe();
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
function observable(state) {
|
|
548
|
+
return new ObserverableManager(state).state$;
|
|
549
|
+
}
|
|
550
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
551
|
+
0 && (module.exports = {
|
|
552
|
+
createDeepProxy,
|
|
553
|
+
observable
|
|
554
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Immutable from 'immutable';
|
|
2
|
+
|
|
3
|
+
declare class Wrapper<T extends any> {
|
|
4
|
+
value: any;
|
|
5
|
+
keyPath: string[];
|
|
6
|
+
originalObject: () => Immutable.Map<keyof T, T[keyof T]>;
|
|
7
|
+
onSet: OnSet<T>;
|
|
8
|
+
onUse: OnUse<T>;
|
|
9
|
+
private proxy;
|
|
10
|
+
constructor(value: any, keyPath: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>);
|
|
11
|
+
valueOf(): any;
|
|
12
|
+
get(): T extends (...args: any[]) => infer R ? R : T;
|
|
13
|
+
peek(): T extends (...args: any[]) => infer R ? R : T;
|
|
14
|
+
get __keyPath(): string[];
|
|
15
|
+
get __self(): {
|
|
16
|
+
value: any;
|
|
17
|
+
keyPath: string[];
|
|
18
|
+
};
|
|
19
|
+
toString(): string;
|
|
20
|
+
toJSON(): string;
|
|
21
|
+
set(value: T): void;
|
|
22
|
+
use(): T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Unwrap<T> = T extends Wrapper<infer U> ? U : T;
|
|
26
|
+
type FunctionType = (...args: any[]) => any;
|
|
27
|
+
type WrapTypeForNormal<P extends object> = {
|
|
28
|
+
get(): P extends (...args: any[]) => infer R ? R : P;
|
|
29
|
+
peek(): P extends (...args: any[]) => infer R ? R : P;
|
|
30
|
+
set(value: P): void;
|
|
31
|
+
use(): P;
|
|
32
|
+
__keyPath: string[];
|
|
33
|
+
};
|
|
34
|
+
type WrapTypeForFunc<P extends object> = {
|
|
35
|
+
get(): P extends (...args: any[]) => infer R ? R : P;
|
|
36
|
+
peek(): P extends (...args: any[]) => infer R ? R : P;
|
|
37
|
+
use(): P;
|
|
38
|
+
__keyPath: string[];
|
|
39
|
+
};
|
|
40
|
+
type WrapType<T extends object, P extends object> = (P extends FunctionType ? WrapTypeForFunc<P> : WrapTypeForNormal<P>) & T;
|
|
41
|
+
type DeepProxy<T> = {
|
|
42
|
+
[P in keyof T]: T[P] extends object ? WrapType<DeepProxy<T[P]>, T[P]> : Wrapper<T[P]>;
|
|
43
|
+
};
|
|
44
|
+
type OnSet<T extends any> = (value: T, keyPath: string[]) => void;
|
|
45
|
+
type OnUse<T extends any> = (keyPath: string[]) => T;
|
|
46
|
+
|
|
47
|
+
declare function createDeepProxy<T extends object>(target: T, path: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>, notifyChange: () => void): DeepProxy<T>;
|
|
48
|
+
|
|
49
|
+
type Observable<T> = DeepProxy<T> & {
|
|
50
|
+
useSelector: <R>(selector: (state: T) => R) => Unwrap<R>;
|
|
51
|
+
useObserve: <P>(selector: (state: T) => P, onChange: (value: Unwrap<P>) => void) => void;
|
|
52
|
+
batch: (batchAction: () => void) => void;
|
|
53
|
+
peek: () => T;
|
|
54
|
+
observe: <P>(selector: (state: T) => P, onChange: (value: P) => void) => () => void;
|
|
55
|
+
__immutable__: Immutable.Map<keyof T, T[keyof T]>;
|
|
56
|
+
};
|
|
57
|
+
declare function observable<T extends object>(state: T): Observable<T>;
|
|
58
|
+
|
|
59
|
+
export { type DeepProxy, type Observable, createDeepProxy, observable };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Immutable from 'immutable';
|
|
2
|
+
|
|
3
|
+
declare class Wrapper<T extends any> {
|
|
4
|
+
value: any;
|
|
5
|
+
keyPath: string[];
|
|
6
|
+
originalObject: () => Immutable.Map<keyof T, T[keyof T]>;
|
|
7
|
+
onSet: OnSet<T>;
|
|
8
|
+
onUse: OnUse<T>;
|
|
9
|
+
private proxy;
|
|
10
|
+
constructor(value: any, keyPath: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>);
|
|
11
|
+
valueOf(): any;
|
|
12
|
+
get(): T extends (...args: any[]) => infer R ? R : T;
|
|
13
|
+
peek(): T extends (...args: any[]) => infer R ? R : T;
|
|
14
|
+
get __keyPath(): string[];
|
|
15
|
+
get __self(): {
|
|
16
|
+
value: any;
|
|
17
|
+
keyPath: string[];
|
|
18
|
+
};
|
|
19
|
+
toString(): string;
|
|
20
|
+
toJSON(): string;
|
|
21
|
+
set(value: T): void;
|
|
22
|
+
use(): T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Unwrap<T> = T extends Wrapper<infer U> ? U : T;
|
|
26
|
+
type FunctionType = (...args: any[]) => any;
|
|
27
|
+
type WrapTypeForNormal<P extends object> = {
|
|
28
|
+
get(): P extends (...args: any[]) => infer R ? R : P;
|
|
29
|
+
peek(): P extends (...args: any[]) => infer R ? R : P;
|
|
30
|
+
set(value: P): void;
|
|
31
|
+
use(): P;
|
|
32
|
+
__keyPath: string[];
|
|
33
|
+
};
|
|
34
|
+
type WrapTypeForFunc<P extends object> = {
|
|
35
|
+
get(): P extends (...args: any[]) => infer R ? R : P;
|
|
36
|
+
peek(): P extends (...args: any[]) => infer R ? R : P;
|
|
37
|
+
use(): P;
|
|
38
|
+
__keyPath: string[];
|
|
39
|
+
};
|
|
40
|
+
type WrapType<T extends object, P extends object> = (P extends FunctionType ? WrapTypeForFunc<P> : WrapTypeForNormal<P>) & T;
|
|
41
|
+
type DeepProxy<T> = {
|
|
42
|
+
[P in keyof T]: T[P] extends object ? WrapType<DeepProxy<T[P]>, T[P]> : Wrapper<T[P]>;
|
|
43
|
+
};
|
|
44
|
+
type OnSet<T extends any> = (value: T, keyPath: string[]) => void;
|
|
45
|
+
type OnUse<T extends any> = (keyPath: string[]) => T;
|
|
46
|
+
|
|
47
|
+
declare function createDeepProxy<T extends object>(target: T, path: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>, notifyChange: () => void): DeepProxy<T>;
|
|
48
|
+
|
|
49
|
+
type Observable<T> = DeepProxy<T> & {
|
|
50
|
+
useSelector: <R>(selector: (state: T) => R) => Unwrap<R>;
|
|
51
|
+
useObserve: <P>(selector: (state: T) => P, onChange: (value: Unwrap<P>) => void) => void;
|
|
52
|
+
batch: (batchAction: () => void) => void;
|
|
53
|
+
peek: () => T;
|
|
54
|
+
observe: <P>(selector: (state: T) => P, onChange: (value: P) => void) => () => void;
|
|
55
|
+
__immutable__: Immutable.Map<keyof T, T[keyof T]>;
|
|
56
|
+
};
|
|
57
|
+
declare function observable<T extends object>(state: T): Observable<T>;
|
|
58
|
+
|
|
59
|
+
export { type DeepProxy, type Observable, createDeepProxy, observable };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
// src/proxy/wrapper.ts
|
|
2
|
+
var Wrapper = class {
|
|
3
|
+
value;
|
|
4
|
+
keyPath = [];
|
|
5
|
+
originalObject;
|
|
6
|
+
onSet;
|
|
7
|
+
onUse;
|
|
8
|
+
proxy = new Proxy(this, {
|
|
9
|
+
get(target, property) {
|
|
10
|
+
if (property in target) {
|
|
11
|
+
return target[property];
|
|
12
|
+
} else {
|
|
13
|
+
return target.value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
constructor(value, keyPath = [], originalObject, onSet, onUse) {
|
|
18
|
+
this.value = value;
|
|
19
|
+
this.keyPath = keyPath;
|
|
20
|
+
this.originalObject = originalObject;
|
|
21
|
+
this.onSet = onSet;
|
|
22
|
+
this.onUse = onUse;
|
|
23
|
+
return this.proxy;
|
|
24
|
+
}
|
|
25
|
+
valueOf() {
|
|
26
|
+
return this.value;
|
|
27
|
+
}
|
|
28
|
+
get() {
|
|
29
|
+
return this.peek();
|
|
30
|
+
}
|
|
31
|
+
peek() {
|
|
32
|
+
const value = this.originalObject().getIn(this.keyPath);
|
|
33
|
+
if (typeof value === "function") {
|
|
34
|
+
return value.bind(this.originalObject)();
|
|
35
|
+
} else {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
get __keyPath() {
|
|
40
|
+
return this.keyPath;
|
|
41
|
+
}
|
|
42
|
+
get __self() {
|
|
43
|
+
return {
|
|
44
|
+
value: this.value,
|
|
45
|
+
keyPath: this.keyPath
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
toString() {
|
|
49
|
+
return this.keyPath.join(".");
|
|
50
|
+
}
|
|
51
|
+
toJSON() {
|
|
52
|
+
return JSON.stringify(this.keyPath);
|
|
53
|
+
}
|
|
54
|
+
set(value) {
|
|
55
|
+
this.onSet(value, this.keyPath);
|
|
56
|
+
}
|
|
57
|
+
use() {
|
|
58
|
+
return this.onUse(this.keyPath);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/proxy/wrapObject.ts
|
|
63
|
+
import Immutable from "immutable";
|
|
64
|
+
function wrapObject(obj, keyPath = [], originalObject, onSet, onUse, _2) {
|
|
65
|
+
return new Proxy(obj, {
|
|
66
|
+
get(target, prop, receiver) {
|
|
67
|
+
switch (prop) {
|
|
68
|
+
case "get":
|
|
69
|
+
return () => originalObject().getIn(keyPath);
|
|
70
|
+
case "peek":
|
|
71
|
+
return () => originalObject().getIn(keyPath);
|
|
72
|
+
case "set":
|
|
73
|
+
return (value) => {
|
|
74
|
+
onSet && onSet(value, keyPath);
|
|
75
|
+
};
|
|
76
|
+
case "use":
|
|
77
|
+
return () => {
|
|
78
|
+
return onUse(keyPath);
|
|
79
|
+
};
|
|
80
|
+
case "__keyPath":
|
|
81
|
+
return keyPath;
|
|
82
|
+
case "isImmutableList":
|
|
83
|
+
return Immutable.isList(target);
|
|
84
|
+
case "isImmutableMap":
|
|
85
|
+
return Immutable.isMap(target);
|
|
86
|
+
case "__self":
|
|
87
|
+
return target;
|
|
88
|
+
// list functions
|
|
89
|
+
case "delete":
|
|
90
|
+
case "remove":
|
|
91
|
+
return (value) => {
|
|
92
|
+
if (Immutable.isList(originalObject().getIn(keyPath))) {
|
|
93
|
+
originalObject().getIn(keyPath).delete(value);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
case "push":
|
|
97
|
+
return (...values) => {
|
|
98
|
+
const targetList = originalObject().getIn(keyPath);
|
|
99
|
+
if (!Immutable.isList(targetList) && !Array.isArray(targetList)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
onSet && onSet([...targetList, ...values], keyPath);
|
|
103
|
+
};
|
|
104
|
+
// default
|
|
105
|
+
default:
|
|
106
|
+
return Reflect.get(target, prop, receiver);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/proxy/index.ts
|
|
113
|
+
function createDeepProxy(target, path = [], originalObject, onSet, onUse, notifyChange) {
|
|
114
|
+
return new Proxy(target, {
|
|
115
|
+
get(_target, property) {
|
|
116
|
+
if (property === "__self") {
|
|
117
|
+
return _target;
|
|
118
|
+
}
|
|
119
|
+
const keyPath = path.concat([property.toString()]);
|
|
120
|
+
const realValue = originalObject().getIn(keyPath);
|
|
121
|
+
if (typeof realValue === "object" && realValue !== null) {
|
|
122
|
+
return wrapObject(
|
|
123
|
+
// @ts-ignore
|
|
124
|
+
createDeepProxy(
|
|
125
|
+
realValue,
|
|
126
|
+
path.concat([property.toString()]),
|
|
127
|
+
originalObject,
|
|
128
|
+
onSet,
|
|
129
|
+
onUse
|
|
130
|
+
),
|
|
131
|
+
keyPath,
|
|
132
|
+
originalObject,
|
|
133
|
+
onSet,
|
|
134
|
+
onUse,
|
|
135
|
+
notifyChange
|
|
136
|
+
// realValue
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return new Wrapper(realValue, keyPath, originalObject, onSet, onUse);
|
|
140
|
+
},
|
|
141
|
+
set(_target, property, value, receiver) {
|
|
142
|
+
return Reflect.set(_target, property, value, receiver);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/manager.ts
|
|
148
|
+
import { BehaviorSubject } from "rxjs";
|
|
149
|
+
|
|
150
|
+
// src/hooks/use.ts
|
|
151
|
+
import { useCallback, useEffect, useRef as useRef2 } from "react";
|
|
152
|
+
import isEqual3 from "react-fast-compare";
|
|
153
|
+
|
|
154
|
+
// src/utils/subscribe.ts
|
|
155
|
+
import isEqual from "react-fast-compare";
|
|
156
|
+
import {
|
|
157
|
+
defaultIfEmpty,
|
|
158
|
+
filter,
|
|
159
|
+
map,
|
|
160
|
+
pairwise,
|
|
161
|
+
startWith
|
|
162
|
+
} from "rxjs";
|
|
163
|
+
import _ from "lodash";
|
|
164
|
+
function generateSubForSpecificChange(config) {
|
|
165
|
+
const defaultValue = config.subject.value ? config.subject.value : config.defaultData ? config.filter(config.defaultData) : void 0;
|
|
166
|
+
const defaultFilteredData = defaultValue !== void 0 ? config.filter(defaultValue) : void 0;
|
|
167
|
+
const sub = config.subject.pipe(
|
|
168
|
+
startWith(void 0),
|
|
169
|
+
map((data) => {
|
|
170
|
+
if (data === void 0) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
return _.cloneDeep(config.filter(data));
|
|
174
|
+
}),
|
|
175
|
+
pairwise(),
|
|
176
|
+
filter(([prev, next]) => {
|
|
177
|
+
return !isEqual(prev, next);
|
|
178
|
+
}),
|
|
179
|
+
defaultIfEmpty([defaultFilteredData, defaultFilteredData])
|
|
180
|
+
);
|
|
181
|
+
return sub;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/hooks/useState.ts
|
|
185
|
+
import { useRef, useState as ReactUseState } from "react";
|
|
186
|
+
import { isEqual as isEqual2 } from "lodash";
|
|
187
|
+
|
|
188
|
+
// src/utils/isSSR.ts
|
|
189
|
+
var isBrowser = typeof window !== "undefined";
|
|
190
|
+
var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
191
|
+
var isSSR = !(isBrowser || isReactNative);
|
|
192
|
+
|
|
193
|
+
// src/hooks/useState.ts
|
|
194
|
+
function useStateForClient(initialState) {
|
|
195
|
+
const stateValue = useRef(initialState || {});
|
|
196
|
+
const [state, _setState] = ReactUseState(initialState);
|
|
197
|
+
const setState = (value) => {
|
|
198
|
+
const valueIsFunction = typeof value === "function";
|
|
199
|
+
const targetValue = valueIsFunction ? value(stateValue.current) : value;
|
|
200
|
+
if (!isEqual2(targetValue, stateValue.current)) {
|
|
201
|
+
stateValue.current = targetValue;
|
|
202
|
+
_setState(targetValue);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return [state, setState];
|
|
206
|
+
}
|
|
207
|
+
var useState = isSSR ? ReactUseState : useStateForClient;
|
|
208
|
+
|
|
209
|
+
// src/hooks/use.ts
|
|
210
|
+
function useFunc({ keyPath, subSource, getState }) {
|
|
211
|
+
const valueRef = useRef2(getState().getIn(keyPath));
|
|
212
|
+
const [value, _setValue] = useState(valueRef.current);
|
|
213
|
+
const setValue = useCallback((nextValue) => {
|
|
214
|
+
if (!isEqual3(valueRef.current, nextValue)) {
|
|
215
|
+
valueRef.current = nextValue;
|
|
216
|
+
_setValue(nextValue);
|
|
217
|
+
}
|
|
218
|
+
}, []);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
const sub = generateSubForSpecificChange({
|
|
221
|
+
subject: subSource,
|
|
222
|
+
filter: () => {
|
|
223
|
+
return getState().getIn(keyPath);
|
|
224
|
+
},
|
|
225
|
+
mark: "useFunc"
|
|
226
|
+
}).subscribe(([_2, next]) => {
|
|
227
|
+
setValue(next);
|
|
228
|
+
});
|
|
229
|
+
return () => {
|
|
230
|
+
if (sub && sub.unsubscribe) {
|
|
231
|
+
sub.unsubscribe();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}, [keyPath, subSource, getState]);
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/hooks/useObserve.ts
|
|
239
|
+
import { useEffect as useEffect2 } from "react";
|
|
240
|
+
|
|
241
|
+
// src/proxy/immutableGetter.ts
|
|
242
|
+
import { Map, List } from "immutable";
|
|
243
|
+
function createProxiedList(list) {
|
|
244
|
+
return new Proxy(list, {
|
|
245
|
+
get(target, key) {
|
|
246
|
+
const numKey = Number(key);
|
|
247
|
+
if (key === "__self") {
|
|
248
|
+
return target;
|
|
249
|
+
}
|
|
250
|
+
if (key === "isImmutableList") {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (Number.isNaN(numKey)) {
|
|
254
|
+
return Reflect.get(target, key);
|
|
255
|
+
}
|
|
256
|
+
const value = target.get(numKey);
|
|
257
|
+
if (Map.isMap(value)) {
|
|
258
|
+
return createProxiedMap(value);
|
|
259
|
+
} else if (List.isList(value)) {
|
|
260
|
+
return createProxiedList(value);
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
},
|
|
264
|
+
set(target, key, value) {
|
|
265
|
+
const numKey = Number(key);
|
|
266
|
+
if (Number.isNaN(numKey)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
target.set(numKey, value);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function createProxiedMap(map2) {
|
|
275
|
+
return new Proxy(map2, {
|
|
276
|
+
get(target, key) {
|
|
277
|
+
const mapValue = target.get(key);
|
|
278
|
+
if (mapValue === void 0) {
|
|
279
|
+
return mapValue;
|
|
280
|
+
}
|
|
281
|
+
if (key === "__self") {
|
|
282
|
+
return target;
|
|
283
|
+
}
|
|
284
|
+
if (key === "isImmutableMap") {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (Map.isMap(mapValue)) {
|
|
288
|
+
return createProxiedMap(
|
|
289
|
+
mapValue
|
|
290
|
+
);
|
|
291
|
+
} else if (List.isList(mapValue)) {
|
|
292
|
+
return createProxiedList(
|
|
293
|
+
mapValue
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return mapValue;
|
|
297
|
+
},
|
|
298
|
+
set(target, key, value) {
|
|
299
|
+
target.set(key, value);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function wrapGetterForMap(data) {
|
|
305
|
+
return createProxiedMap(data);
|
|
306
|
+
}
|
|
307
|
+
function wrapAsImmutable(data) {
|
|
308
|
+
const mapData = Map(data);
|
|
309
|
+
return {
|
|
310
|
+
getter: createProxiedMap(mapData),
|
|
311
|
+
mapData
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/hooks/useObserve.ts
|
|
316
|
+
function useObserve({
|
|
317
|
+
selectorFunction,
|
|
318
|
+
subSource,
|
|
319
|
+
onChangeFunction,
|
|
320
|
+
getState
|
|
321
|
+
}) {
|
|
322
|
+
useEffect2(() => {
|
|
323
|
+
const sub = generateSubForSpecificChange({
|
|
324
|
+
subject: subSource,
|
|
325
|
+
filter: () => {
|
|
326
|
+
return selectorFunction(wrapGetterForMap(getState()));
|
|
327
|
+
},
|
|
328
|
+
mark: "useObserve"
|
|
329
|
+
}).subscribe(([_2, next]) => {
|
|
330
|
+
onChangeFunction(next);
|
|
331
|
+
});
|
|
332
|
+
return () => {
|
|
333
|
+
if (sub && sub.unsubscribe) {
|
|
334
|
+
sub.unsubscribe();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}, [subSource, getState, onChangeFunction, selectorFunction]);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/hooks/useSelector.ts
|
|
341
|
+
import { useCallback as useCallback2, useEffect as useEffect3 } from "react";
|
|
342
|
+
|
|
343
|
+
// src/utils/isImmutableObject.ts
|
|
344
|
+
var IMMUTABLE_ITERABLE = "@@__IMMUTABLE_ITERABLE__@@";
|
|
345
|
+
var IMMUTABLE_RECORD = "@@__IMMUTABLE_RECORD__@@";
|
|
346
|
+
var IMMUTABLE_LIST = "@@__IMMUTABLE_LIST__@@";
|
|
347
|
+
var IMMUTABLE_MAP = "@@__IMMUTABLE_MAP__@@";
|
|
348
|
+
function isImmutableObject(obj) {
|
|
349
|
+
if (typeof obj !== "object") {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const proto = Object.getPrototypeOf(obj);
|
|
353
|
+
if (proto[IMMUTABLE_ITERABLE] || proto[IMMUTABLE_RECORD] || proto[IMMUTABLE_LIST] || proto[IMMUTABLE_MAP]) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/hooks/useSelector.ts
|
|
360
|
+
function useSelector({
|
|
361
|
+
selectorFunction,
|
|
362
|
+
subSource,
|
|
363
|
+
getState
|
|
364
|
+
}) {
|
|
365
|
+
const wrappedSelectorFunction = useCallback2(
|
|
366
|
+
(getJS = false) => () => {
|
|
367
|
+
const state = getState();
|
|
368
|
+
const res = selectorFunction(wrapGetterForMap(state));
|
|
369
|
+
if (isImmutableObject(res)) {
|
|
370
|
+
if (getJS) {
|
|
371
|
+
return state.toJS();
|
|
372
|
+
}
|
|
373
|
+
return state;
|
|
374
|
+
}
|
|
375
|
+
return res;
|
|
376
|
+
},
|
|
377
|
+
[selectorFunction, getState]
|
|
378
|
+
);
|
|
379
|
+
const [value, setValue] = useState(wrappedSelectorFunction(true)());
|
|
380
|
+
useEffect3(() => {
|
|
381
|
+
const sub = generateSubForSpecificChange({
|
|
382
|
+
subject: subSource,
|
|
383
|
+
filter: wrappedSelectorFunction(),
|
|
384
|
+
mark: "useSelector"
|
|
385
|
+
}).subscribe(([_2, next]) => {
|
|
386
|
+
if (isImmutableObject(next)) {
|
|
387
|
+
setValue(next.toJS());
|
|
388
|
+
} else {
|
|
389
|
+
setValue(next);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
return () => {
|
|
393
|
+
if (sub && sub.unsubscribe) {
|
|
394
|
+
sub.unsubscribe();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}, [wrappedSelectorFunction, getState, subSource]);
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/manager.ts
|
|
402
|
+
var ObserverableManager = class {
|
|
403
|
+
state;
|
|
404
|
+
subject;
|
|
405
|
+
_state$;
|
|
406
|
+
batchProcessing = false;
|
|
407
|
+
stateGetter;
|
|
408
|
+
get state$() {
|
|
409
|
+
return new Proxy(this._state$, {
|
|
410
|
+
get: (target, property, receiver) => {
|
|
411
|
+
switch (property) {
|
|
412
|
+
case "useSelector":
|
|
413
|
+
return this.useSelector;
|
|
414
|
+
case "useObserve":
|
|
415
|
+
return this.useObserve;
|
|
416
|
+
case "batch":
|
|
417
|
+
return this.batch;
|
|
418
|
+
case "peek":
|
|
419
|
+
return this.peek;
|
|
420
|
+
case "observe":
|
|
421
|
+
return this.observe;
|
|
422
|
+
case "__immutable__":
|
|
423
|
+
return this.state;
|
|
424
|
+
default:
|
|
425
|
+
return Reflect.get(target, property, receiver);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
set: () => {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
constructor(state) {
|
|
434
|
+
const wrappedState = wrapAsImmutable(state);
|
|
435
|
+
this.state = wrappedState.mapData;
|
|
436
|
+
this.stateGetter = wrappedState.getter;
|
|
437
|
+
this.subject = new BehaviorSubject(this.state);
|
|
438
|
+
this._state$ = createDeepProxy(
|
|
439
|
+
state,
|
|
440
|
+
[],
|
|
441
|
+
this.getCurrentState,
|
|
442
|
+
this.onSet,
|
|
443
|
+
this.onUse,
|
|
444
|
+
this.notifyChange
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* This method will be utilized by the 'get' method of the Proxy and the subscription logic within hooks,
|
|
449
|
+
* with the aim of obtaining the latest 'state' reference.
|
|
450
|
+
* @returns
|
|
451
|
+
*/
|
|
452
|
+
getCurrentState = () => {
|
|
453
|
+
return this.state;
|
|
454
|
+
};
|
|
455
|
+
onSet = (value, keyPath) => {
|
|
456
|
+
this.state = this.state.setIn(keyPath, value);
|
|
457
|
+
this.notifyChange();
|
|
458
|
+
};
|
|
459
|
+
notifyChange = () => {
|
|
460
|
+
if (this.batchProcessing) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this.subject.next(this.state);
|
|
464
|
+
};
|
|
465
|
+
onUse = (keyPath) => {
|
|
466
|
+
return useFunc({
|
|
467
|
+
keyPath,
|
|
468
|
+
subSource: this.subject,
|
|
469
|
+
getState: this.getCurrentState
|
|
470
|
+
});
|
|
471
|
+
};
|
|
472
|
+
useSelector = (selector) => {
|
|
473
|
+
return useSelector({
|
|
474
|
+
selectorFunction: selector,
|
|
475
|
+
subSource: this.subject,
|
|
476
|
+
getState: this.getCurrentState
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
useObserve = (selector, onChange) => {
|
|
480
|
+
useObserve({
|
|
481
|
+
selectorFunction: selector,
|
|
482
|
+
subSource: this.subject,
|
|
483
|
+
onChangeFunction: onChange,
|
|
484
|
+
getState: this.getCurrentState
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
batch = (batchAction) => {
|
|
488
|
+
this.batchProcessing = true;
|
|
489
|
+
batchAction();
|
|
490
|
+
this.batchProcessing = false;
|
|
491
|
+
this.notifyChange();
|
|
492
|
+
};
|
|
493
|
+
peek = () => {
|
|
494
|
+
return this.state.toJS();
|
|
495
|
+
};
|
|
496
|
+
get = () => {
|
|
497
|
+
return this.peek();
|
|
498
|
+
};
|
|
499
|
+
observe = (selector, onChange) => {
|
|
500
|
+
const sub = generateSubForSpecificChange({
|
|
501
|
+
subject: this.subject,
|
|
502
|
+
filter: () => {
|
|
503
|
+
const res = selector(wrapGetterForMap(this.state));
|
|
504
|
+
return res;
|
|
505
|
+
}
|
|
506
|
+
}).subscribe(([_2, next]) => {
|
|
507
|
+
onChange(next);
|
|
508
|
+
});
|
|
509
|
+
return () => {
|
|
510
|
+
if (sub && sub.unsubscribe) {
|
|
511
|
+
sub.unsubscribe();
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
};
|
|
515
|
+
};
|
|
516
|
+
function observable(state) {
|
|
517
|
+
return new ObserverableManager(state).state$;
|
|
518
|
+
}
|
|
519
|
+
export {
|
|
520
|
+
createDeepProxy,
|
|
521
|
+
observable
|
|
522
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@international-iot-association/rct-state",
|
|
3
|
+
"version": "3.0.0-rc.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org/"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/International-IoT-Association/MES.git",
|
|
12
|
+
"directory": "electron-station-plugins/packages/rct-state"
|
|
13
|
+
},
|
|
14
|
+
"description": "Legend-State 风格的 RxJS + Immutable 响应式状态库。",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"clean": "rimraf dist .turbo"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"immutable": "^4.3.2",
|
|
36
|
+
"lodash": "^4.17.21",
|
|
37
|
+
"react-fast-compare": "^3.2.2",
|
|
38
|
+
"rxjs": "^7.8.1"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@international-iot-association/tsconfig": "1.0.0-rc.1",
|
|
45
|
+
"@types/lodash": "^4.17.13",
|
|
46
|
+
"@types/react": "^19.2.7",
|
|
47
|
+
"react": "^19.2.1",
|
|
48
|
+
"tsup": "^8.4.0",
|
|
49
|
+
"typescript": "^5.9.3"
|
|
50
|
+
},
|
|
51
|
+
"x-internal-version": "1.0.0",
|
|
52
|
+
"x-source-revision": "410a2f3b85ef03b678f04f22ea0a47718f5ddae1"
|
|
53
|
+
}
|