react-native-hox 0.0.1-beta → 0.0.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 +149 -76
- package/dist/index.d.mts +34 -37
- package/dist/index.d.ts +34 -37
- package/dist/index.js +146 -151
- package/dist/index.mjs +143 -151
- package/package.json +19 -5
- package/README.en.md +0 -36
package/dist/index.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var useSyncExternalStoreExports = require('use-sync-external-store/shim/with-selector');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var useSyncExternalStoreExports__default = /*#__PURE__*/_interopDefault(useSyncExternalStoreExports);
|
|
4
8
|
|
|
5
9
|
// src/createModel.ts
|
|
6
10
|
|
|
7
11
|
// src/config.ts
|
|
12
|
+
var cachedDefaultWebStorage;
|
|
13
|
+
var hasCachedDefaultWebStorage = false;
|
|
8
14
|
function getDefaultWebStorage() {
|
|
15
|
+
if (hasCachedDefaultWebStorage) {
|
|
16
|
+
return cachedDefaultWebStorage;
|
|
17
|
+
}
|
|
18
|
+
hasCachedDefaultWebStorage = true;
|
|
9
19
|
try {
|
|
10
20
|
const anyGlobal = globalThis;
|
|
11
21
|
const storage = anyGlobal == null ? void 0 : anyGlobal.localStorage;
|
|
@@ -15,57 +25,32 @@ function getDefaultWebStorage() {
|
|
|
15
25
|
const testKey = "__react_native_hox_test__";
|
|
16
26
|
storage.setItem(testKey, "1");
|
|
17
27
|
storage.removeItem(testKey);
|
|
18
|
-
|
|
28
|
+
cachedDefaultWebStorage = {
|
|
19
29
|
getItem: (key) => storage.getItem(key),
|
|
20
30
|
setItem: (key, value) => storage.setItem(key, value),
|
|
21
31
|
removeItem: (key) => storage.removeItem(key)
|
|
22
32
|
};
|
|
33
|
+
return cachedDefaultWebStorage;
|
|
23
34
|
} catch {
|
|
35
|
+
cachedDefaultWebStorage = void 0;
|
|
24
36
|
return void 0;
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
|
-
var config = {
|
|
28
|
-
storage: getDefaultWebStorage()
|
|
29
|
-
};
|
|
30
|
-
var setup = (options) => {
|
|
31
|
-
Object.assign(config, options);
|
|
32
|
-
};
|
|
33
|
-
var getConfig = () => config;
|
|
34
39
|
|
|
35
40
|
// src/vanilla.ts
|
|
36
41
|
var createStore = (initialState) => {
|
|
37
42
|
let state = initialState;
|
|
38
43
|
const listeners = /* @__PURE__ */ new Set();
|
|
39
|
-
const
|
|
40
|
-
const setState = (partial, replace = false) => {
|
|
44
|
+
const setState = (partial, replace) => {
|
|
41
45
|
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
42
|
-
if (Object.is(nextState, state)) {
|
|
43
|
-
|
|
46
|
+
if (!Object.is(nextState, state)) {
|
|
47
|
+
const previousState = state;
|
|
48
|
+
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
49
|
+
listeners.forEach((listener) => listener(state, previousState));
|
|
44
50
|
}
|
|
45
|
-
const previousState = state;
|
|
46
|
-
const isNextObject = typeof nextState === "object" && nextState !== null;
|
|
47
|
-
const isPrevObject = typeof previousState === "object" && previousState !== null;
|
|
48
|
-
const shouldReplace = replace || !isNextObject || !isPrevObject;
|
|
49
|
-
if (shouldReplace) {
|
|
50
|
-
state = nextState;
|
|
51
|
-
} else {
|
|
52
|
-
const patch = nextState;
|
|
53
|
-
const prevObj = previousState;
|
|
54
|
-
let changed = false;
|
|
55
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
56
|
-
if (!Object.is(prevObj[key], value)) {
|
|
57
|
-
changed = true;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (!changed) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
state = Object.assign({}, previousState, nextState);
|
|
65
|
-
}
|
|
66
|
-
const currentListeners = new Set(listeners);
|
|
67
|
-
currentListeners.forEach((listener) => listener(state, previousState));
|
|
68
51
|
};
|
|
52
|
+
const getState = () => state;
|
|
53
|
+
const getInitialState = () => initialState;
|
|
69
54
|
const subscribe = (listener) => {
|
|
70
55
|
listeners.add(listener);
|
|
71
56
|
return () => listeners.delete(listener);
|
|
@@ -73,11 +58,11 @@ var createStore = (initialState) => {
|
|
|
73
58
|
const destroy = () => {
|
|
74
59
|
listeners.clear();
|
|
75
60
|
};
|
|
76
|
-
return { getState, setState, subscribe, destroy };
|
|
61
|
+
return { getState, getInitialState, setState, subscribe, destroy };
|
|
77
62
|
};
|
|
78
63
|
|
|
79
64
|
// src/utils.ts
|
|
80
|
-
|
|
65
|
+
function shallow(objA, objB) {
|
|
81
66
|
if (Object.is(objA, objB)) {
|
|
82
67
|
return true;
|
|
83
68
|
}
|
|
@@ -95,7 +80,7 @@ var shallow = (objA, objB) => {
|
|
|
95
80
|
}
|
|
96
81
|
}
|
|
97
82
|
return true;
|
|
98
|
-
}
|
|
83
|
+
}
|
|
99
84
|
function debounce(func, wait) {
|
|
100
85
|
let timeout;
|
|
101
86
|
let lastArgs;
|
|
@@ -107,6 +92,7 @@ function debounce(func, wait) {
|
|
|
107
92
|
timeout = setTimeout(() => {
|
|
108
93
|
func.apply(context, args);
|
|
109
94
|
lastArgs = void 0;
|
|
95
|
+
timeout = void 0;
|
|
110
96
|
}, wait);
|
|
111
97
|
};
|
|
112
98
|
debounced.flush = () => {
|
|
@@ -114,164 +100,173 @@ function debounce(func, wait) {
|
|
|
114
100
|
clearTimeout(timeout);
|
|
115
101
|
func.apply(context, lastArgs);
|
|
116
102
|
lastArgs = void 0;
|
|
103
|
+
timeout = void 0;
|
|
117
104
|
}
|
|
118
105
|
};
|
|
119
106
|
debounced.cancel = () => {
|
|
120
107
|
clearTimeout(timeout);
|
|
121
108
|
lastArgs = void 0;
|
|
109
|
+
timeout = void 0;
|
|
122
110
|
};
|
|
123
111
|
return debounced;
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
// src/createModel.ts
|
|
115
|
+
var { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports__default.default;
|
|
116
|
+
var identity = (v) => v;
|
|
117
|
+
function isInvalidHookCallError(error) {
|
|
118
|
+
return error instanceof Error && /Invalid hook call|Hooks can only be called/i.test(error.message);
|
|
119
|
+
}
|
|
127
120
|
function createModel(initialState, options) {
|
|
121
|
+
var _a;
|
|
128
122
|
const initialValue = typeof initialState === "function" ? initialState() : initialState;
|
|
129
123
|
const store = createStore(initialValue);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (equalityRef.current(lastSelectionRef.current, nextSelection)) {
|
|
157
|
-
lastStateRef.current = state;
|
|
158
|
-
return lastSelectionRef.current;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
hasSelectionRef.current = true;
|
|
162
|
-
lastStateRef.current = state;
|
|
163
|
-
lastSelectionRef.current = nextSelection;
|
|
164
|
-
return nextSelection;
|
|
165
|
-
};
|
|
166
|
-
return react.useSyncExternalStore(subscribe, getSelection, getSelection);
|
|
167
|
-
});
|
|
168
|
-
Object.defineProperty(useModel, "data", {
|
|
124
|
+
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : console;
|
|
125
|
+
setupModelSideEffects(store, options, logger);
|
|
126
|
+
let isDestroyed = false;
|
|
127
|
+
const setState = (partial, replace) => {
|
|
128
|
+
var _a2;
|
|
129
|
+
if (isDestroyed && ((_a2 = options == null ? void 0 : options.strict) == null ? void 0 : _a2.forbidSetStateAfterDestroy)) {
|
|
130
|
+
const error = new Error("[react-native-hox] model \u5DF2\u9500\u6BC1\uFF0C\u65E0\u6CD5\u518D\u8C03\u7528 setState\u3002");
|
|
131
|
+
logger.error(error);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
store.setState(partial, replace);
|
|
135
|
+
};
|
|
136
|
+
const destroy = () => {
|
|
137
|
+
isDestroyed = true;
|
|
138
|
+
store.destroy();
|
|
139
|
+
};
|
|
140
|
+
const reset = () => store.setState(initialValue, true);
|
|
141
|
+
const subscribe = createSubscribe(store);
|
|
142
|
+
const data = {
|
|
143
|
+
getState: store.getState,
|
|
144
|
+
setState,
|
|
145
|
+
subscribe,
|
|
146
|
+
destroy,
|
|
147
|
+
reset
|
|
148
|
+
};
|
|
149
|
+
Object.defineProperty(data, "state", {
|
|
169
150
|
get: () => store.getState()
|
|
170
151
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
152
|
+
const hook = createModelHook(store);
|
|
153
|
+
const model = {
|
|
154
|
+
getState: hook,
|
|
155
|
+
useState: hook,
|
|
156
|
+
use: hook,
|
|
157
|
+
// Add 'use' alias
|
|
158
|
+
setState,
|
|
159
|
+
reset,
|
|
160
|
+
destroy,
|
|
161
|
+
data
|
|
162
|
+
};
|
|
163
|
+
const modelFn = function(selector, equalityFn) {
|
|
164
|
+
return hook(selector, equalityFn);
|
|
175
165
|
};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
Object.assign(modelFn, model);
|
|
167
|
+
return modelFn;
|
|
168
|
+
}
|
|
169
|
+
function createSubscribe(store) {
|
|
170
|
+
return ((arg1, arg2, arg3) => {
|
|
180
171
|
var _a;
|
|
181
172
|
if (typeof arg1 === "function" && typeof arg2 !== "function") {
|
|
182
|
-
return
|
|
173
|
+
return store.subscribe(arg1);
|
|
183
174
|
}
|
|
184
175
|
const selector = arg1;
|
|
185
176
|
const listener = arg2;
|
|
186
177
|
const options = arg3 != null ? arg3 : {};
|
|
187
178
|
const equalityFn = (_a = options.equalityFn) != null ? _a : Object.is;
|
|
188
|
-
let
|
|
179
|
+
let lastSelected = selector(store.getState());
|
|
189
180
|
if (options.fireImmediately) {
|
|
190
|
-
listener(
|
|
181
|
+
listener(lastSelected, lastSelected);
|
|
191
182
|
}
|
|
192
|
-
return
|
|
183
|
+
return store.subscribe((nextState) => {
|
|
193
184
|
const nextSelected = selector(nextState);
|
|
194
|
-
|
|
195
|
-
if (equalityFn(prevSelectedFromPrevState, nextSelected)) {
|
|
185
|
+
if (equalityFn(lastSelected, nextSelected)) {
|
|
196
186
|
return;
|
|
197
187
|
}
|
|
198
|
-
prevSelected =
|
|
199
|
-
|
|
188
|
+
const prevSelected = lastSelected;
|
|
189
|
+
lastSelected = nextSelected;
|
|
190
|
+
listener(nextSelected, prevSelected);
|
|
200
191
|
});
|
|
201
|
-
};
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function createModelHook(store) {
|
|
195
|
+
const useModel = ((selector, equalityFn = shallow) => {
|
|
196
|
+
try {
|
|
197
|
+
return useSyncExternalStoreWithSelector(
|
|
198
|
+
store.subscribe,
|
|
199
|
+
store.getState,
|
|
200
|
+
store.getInitialState,
|
|
201
|
+
selector != null ? selector : identity,
|
|
202
|
+
equalityFn
|
|
203
|
+
);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (isInvalidHookCallError(error)) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"[react-native-hox] \u4F60\u6B63\u5728\u7EC4\u4EF6\u5916\u8C03\u7528 Hook \u8BFB\u53D6\u72B6\u6001\uFF1B\u8BF7\u5728\u7EC4\u4EF6\u5185\u4F7F\u7528 `model()` / `model.use()` / `model.getState()` / `model.useState()`\uFF0C\u7EC4\u4EF6\u5916\u8BF7\u4F7F\u7528 `model.data.getState()` / `model.data.state`\u3002"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
202
213
|
return useModel;
|
|
203
214
|
}
|
|
204
|
-
function setupModelSideEffects(store, options) {
|
|
205
|
-
var _a, _b, _c;
|
|
206
|
-
const { storage } = getConfig();
|
|
215
|
+
function setupModelSideEffects(store, options, logger) {
|
|
216
|
+
var _a, _b, _c, _d;
|
|
207
217
|
const persist = typeof (options == null ? void 0 : options.persist) === "string" ? { key: options.persist } : options == null ? void 0 : options.persist;
|
|
208
218
|
const persistKey = persist == null ? void 0 : persist.key;
|
|
209
|
-
const
|
|
219
|
+
const storage = (_a = persist == null ? void 0 : persist.storage) != null ? _a : getDefaultWebStorage();
|
|
210
220
|
if (!persistKey) {
|
|
211
|
-
if (syncEnabled) {
|
|
212
|
-
console.warn(
|
|
213
|
-
"[react-native-hox] `sync` \u9700\u8981 `persist` \u63D0\u4F9B key \u624D\u80FD\u5EFA\u7ACB\u901A\u9053"
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
221
|
return;
|
|
217
222
|
}
|
|
218
|
-
const beforePersist = (
|
|
219
|
-
const beforeRecover = (
|
|
220
|
-
const debounceMs = (
|
|
223
|
+
const beforePersist = (_b = persist == null ? void 0 : persist.beforePersist) != null ? _b : ((s) => s);
|
|
224
|
+
const beforeRecover = (_c = persist == null ? void 0 : persist.beforeRecover) != null ? _c : ((v) => v);
|
|
225
|
+
const debounceMs = (_d = persist == null ? void 0 : persist.debounce) != null ? _d : 100;
|
|
221
226
|
if (!storage) {
|
|
222
|
-
|
|
223
|
-
"[react-native-hox] \
|
|
227
|
+
logger.warn(
|
|
228
|
+
"[react-native-hox] persist \u5DF2\u5F00\u542F\u4F46 storage \u4E0D\u53EF\u7528\uFF1AWeb \u9700\u8981 localStorage \u53EF\u7528\uFF1BReact Native/Node \u8BF7\u5728 persist.storage \u4F20\u5165 AsyncStorage/MMKV \u7B49\u3002"
|
|
224
229
|
);
|
|
230
|
+
return;
|
|
225
231
|
}
|
|
226
|
-
let
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
if (syncEnabled && typeof BroadcastChannel !== "undefined") {
|
|
245
|
-
channel = new BroadcastChannel(`hox-sync-${persistKey}`);
|
|
246
|
-
channel.onmessage = (event) => {
|
|
247
|
-
const payload = event.data;
|
|
248
|
-
const nextState = (payload == null ? void 0 : payload.__hox) ? payload.state : payload;
|
|
249
|
-
const source = (payload == null ? void 0 : payload.__hox) ? payload.source : void 0;
|
|
250
|
-
if (source && source === instanceId) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
suppressBroadcast = true;
|
|
254
|
-
store.setState(nextState, true);
|
|
255
|
-
suppressBroadcast = false;
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
const save = debounce((state, s) => {
|
|
259
|
-
if (!s) return;
|
|
232
|
+
let isDestroyed = false;
|
|
233
|
+
Promise.resolve().then(() => storage.getItem(persistKey)).then((val) => {
|
|
234
|
+
if (isDestroyed) return;
|
|
235
|
+
if (!val) return;
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(val);
|
|
238
|
+
const recovered = beforeRecover(parsed);
|
|
239
|
+
store.setState(recovered, false);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
logger.error("[react-native-hox] Failed to parse stored value:", e);
|
|
242
|
+
}
|
|
243
|
+
}).catch((e) => {
|
|
244
|
+
logger.error("[react-native-hox] Recover failed:", e);
|
|
245
|
+
});
|
|
246
|
+
const save = debounce((state) => {
|
|
247
|
+
if (isDestroyed) return;
|
|
260
248
|
try {
|
|
261
249
|
const prepared = beforePersist(state);
|
|
262
|
-
|
|
250
|
+
const result = storage.setItem(persistKey, JSON.stringify(prepared));
|
|
251
|
+
Promise.resolve(result).catch((e) => {
|
|
252
|
+
logger.error("[react-native-hox] Save failed:", e);
|
|
253
|
+
});
|
|
263
254
|
} catch (e) {
|
|
264
|
-
|
|
255
|
+
logger.error("[react-native-hox] Save failed:", e);
|
|
265
256
|
}
|
|
266
257
|
}, debounceMs);
|
|
267
|
-
store.subscribe((state) => {
|
|
268
|
-
save(state
|
|
269
|
-
if (channel && !suppressBroadcast) {
|
|
270
|
-
channel.postMessage({ __hox: true, source: instanceId, state });
|
|
271
|
-
}
|
|
258
|
+
const unsubscribe = store.subscribe((state) => {
|
|
259
|
+
save(state);
|
|
272
260
|
});
|
|
261
|
+
const originalDestroy = store.destroy;
|
|
262
|
+
store.destroy = () => {
|
|
263
|
+
unsubscribe();
|
|
264
|
+
save.flush();
|
|
265
|
+
isDestroyed = true;
|
|
266
|
+
save.cancel();
|
|
267
|
+
originalDestroy();
|
|
268
|
+
};
|
|
273
269
|
}
|
|
274
270
|
|
|
275
271
|
exports.createModel = createModel;
|
|
276
|
-
exports.setup = setup;
|
|
277
272
|
exports.shallow = shallow;
|