react-native-hox 0.0.3 → 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/README.md +59 -140
- package/dist/index.d.mts +39 -29
- package/dist/index.d.ts +39 -29
- package/dist/index.js +1 -254
- package/dist/index.mjs +1 -246
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
# react-native-hox
|
|
2
2
|
|
|
3
|
-
面向 React Native
|
|
3
|
+
面向 React Native 的**极简状态管理**:类型安全、API 零心智负担,单文件架构,包体积极小。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-native-hox)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://www.npmjs.com/package/react-native-hox)
|
|
8
8
|
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 亮点
|
|
12
|
+
|
|
13
|
+
| 特性 | 说明 |
|
|
14
|
+
|------|------|
|
|
15
|
+
| **包体积极小** | 单入口构建,**gzip 后 ESM ~1.5 KB / CJS ~1.6 KB**;运行时仅依赖 `use-sync-external-store`,持久化可选依赖 AsyncStorage |
|
|
16
|
+
| **类型安全** | 全 TypeScript,`createModel` 泛型推导完整,selector 与 setState 类型即文档 |
|
|
17
|
+
| **API 极简** | 一个 `createModel` 搞定:组件内用 Hook,组件外用 `model.data`,无需 Provider |
|
|
18
|
+
| **按需重渲染** | 支持 selector + 可选 `shallow`,只在关注字段变化时更新组件 |
|
|
19
|
+
| **可选持久化** | 内置 persist(默认 AsyncStorage),支持自定义 storage、防抖、迁移钩子 |
|
|
20
|
+
| **React 18 友好** | 基于 `useSyncExternalStore`,无 tearing,兼容并发渲染 |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
9
24
|
## 快速开始
|
|
10
25
|
|
|
11
26
|
### 安装
|
|
@@ -20,8 +35,8 @@ npm i react-native-hox
|
|
|
20
35
|
// stores/user.ts
|
|
21
36
|
import { createModel } from 'react-native-hox';
|
|
22
37
|
|
|
23
|
-
export const userStore = createModel({ name: 'Guest' });
|
|
24
|
-
//
|
|
38
|
+
export const userStore = createModel({ name: 'Guest' });
|
|
39
|
+
// 持久化:createModel({ name: 'Guest' }, { persist: 'user_v1' });
|
|
25
40
|
```
|
|
26
41
|
|
|
27
42
|
```tsx
|
|
@@ -41,198 +56,102 @@ export function Profile() {
|
|
|
41
56
|
}
|
|
42
57
|
```
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### 1) 更新状态:自动合并 / 显式替换
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
import { createModel } from 'react-native-hox';
|
|
50
|
-
|
|
51
|
-
export const profileStore = createModel({ name: 'Guest', role: 'user' as 'user' | 'admin' });
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
import React from 'react';
|
|
56
|
-
import { View, Text, Button } from 'react-native';
|
|
57
|
-
import { profileStore } from './stores/profile';
|
|
59
|
+
---
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
const profile = profileStore.getState();
|
|
61
|
+
## 功能概览
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
<View>
|
|
64
|
-
<Text>{profile.name}</Text>
|
|
65
|
-
<Text>{profile.role}</Text>
|
|
66
|
-
<Button title="Merge Update" onPress={() => profileStore.setState({ name: 'Alice' })} />
|
|
67
|
-
<Button title="Replace" onPress={() => profileStore.setState({ name: 'Bob', role: 'admin' }, true)} />
|
|
68
|
-
</View>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 2) Selector:只在关注的数据变化时重渲染
|
|
63
|
+
### 1) 更新状态:自动合并 / 显式替换
|
|
74
64
|
|
|
75
65
|
```ts
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export const counterStore = createModel({ count: 0, text: 'a' });
|
|
66
|
+
const profileStore = createModel({ name: 'Guest', role: 'user' as 'user' | 'admin' });
|
|
79
67
|
```
|
|
80
68
|
|
|
81
69
|
```tsx
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
import { counterStore } from './stores/counter';
|
|
85
|
-
|
|
86
|
-
export function SelectorExample() {
|
|
87
|
-
const count = counterStore.getState((s) => s.count);
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<View>
|
|
91
|
-
<Text>{count}</Text>
|
|
92
|
-
<Button title="Inc" onPress={() => counterStore.setState((s) => ({ count: s.count + 1 }))} />
|
|
93
|
-
<Button title="Change Text" onPress={() => counterStore.setState({ text: String(Math.random()) })} />
|
|
94
|
-
</View>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
70
|
+
profileStore.setState({ name: 'Alice' }); // 合并
|
|
71
|
+
profileStore.setState({ name: 'Bob', role: 'admin' }, true); // 整体替换
|
|
97
72
|
```
|
|
98
73
|
|
|
99
|
-
|
|
74
|
+
### 2) Selector:只在关注数据变化时重渲染
|
|
100
75
|
|
|
101
76
|
```tsx
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
77
|
+
const count = counterStore.getState((s) => s.count);
|
|
78
|
+
// 返回对象时建议用 shallow
|
|
105
79
|
const picked = counterStore.getState((s) => ({ count: s.count }), shallow);
|
|
106
80
|
```
|
|
107
81
|
|
|
108
82
|
### 3) 组件外读写:Vanilla API
|
|
109
83
|
|
|
110
84
|
```ts
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const name2 = userStore.data.getState().name;
|
|
115
|
-
|
|
116
|
-
const unsub = userStore.data.subscribe((next, prev) => {
|
|
117
|
-
console.log(next, prev);
|
|
118
|
-
});
|
|
119
|
-
unsub();
|
|
85
|
+
const name = userStore.data.state.name;
|
|
86
|
+
userStore.data.getState();
|
|
87
|
+
userStore.data.subscribe((next, prev) => {});
|
|
120
88
|
```
|
|
121
89
|
|
|
122
|
-
### 4)
|
|
123
|
-
|
|
124
|
-
只要提供一个 key 即可开启持久化(默认使用 `@react-native-async-storage/async-storage`,无需重复传 `storage`):
|
|
90
|
+
### 4) 持久化
|
|
125
91
|
|
|
126
92
|
```ts
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
如果你希望替换存储引擎,可以在 `persist.storage` 传入实现同等接口的存储对象:
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
import { createModel } from 'react-native-hox';
|
|
136
|
-
import type { StorageEngine } from 'react-native-hox';
|
|
137
|
-
|
|
138
|
-
const storage: StorageEngine = {
|
|
139
|
-
getItem: async (key) => null,
|
|
140
|
-
setItem: async (key, value) => {},
|
|
141
|
-
removeItem: async (key) => {},
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
export const authStore = createModel({ token: '' }, { persist: { key: 'auth_v1', storage } });
|
|
93
|
+
createModel({ token: '' }, { persist: 'auth_v1' });
|
|
94
|
+
// 或自定义 storage / beforeRecover / beforePersist
|
|
95
|
+
createModel({ token: '' }, { persist: { key: 'auth_v1', storage: myStorage } });
|
|
145
96
|
```
|
|
146
97
|
|
|
147
98
|
### 5) reset / destroy / strict
|
|
148
99
|
|
|
149
100
|
```ts
|
|
150
|
-
import { userStore } from './stores/user';
|
|
151
|
-
|
|
152
101
|
userStore.reset();
|
|
153
102
|
userStore.destroy();
|
|
103
|
+
createModel({ count: 0 }, { strict: { forbidSetStateAfterDestroy: true } });
|
|
154
104
|
```
|
|
155
105
|
|
|
156
|
-
|
|
157
|
-
import { createModel } from 'react-native-hox';
|
|
158
|
-
|
|
159
|
-
export const strictStore = createModel(
|
|
160
|
-
{ count: 0 },
|
|
161
|
-
{ strict: { forbidSetStateAfterDestroy: true } }
|
|
162
|
-
);
|
|
163
|
-
```
|
|
106
|
+
---
|
|
164
107
|
|
|
165
|
-
##
|
|
108
|
+
## 进阶
|
|
166
109
|
|
|
167
|
-
###
|
|
110
|
+
### subscribe(selector):按 slice 订阅
|
|
168
111
|
|
|
169
112
|
```tsx
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const name = userStore.getState((s) => s.name);
|
|
176
|
-
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
const unsub = userStore.data.subscribe(
|
|
179
|
-
(s) => s.name,
|
|
180
|
-
(next, prev) => {
|
|
181
|
-
console.log('name changed:', prev, '->', next);
|
|
182
|
-
},
|
|
183
|
-
{ fireImmediately: true }
|
|
184
|
-
);
|
|
185
|
-
return unsub;
|
|
186
|
-
}, []);
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<View>
|
|
190
|
-
<Text>{name}</Text>
|
|
191
|
-
</View>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
113
|
+
userStore.data.subscribe(
|
|
114
|
+
(s) => s.name,
|
|
115
|
+
(next, prev) => console.log(next, prev),
|
|
116
|
+
{ fireImmediately: true }
|
|
117
|
+
);
|
|
194
118
|
```
|
|
195
119
|
|
|
196
|
-
###
|
|
120
|
+
### 持久化迁移
|
|
197
121
|
|
|
198
122
|
```ts
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
export const userPersistStore = createModel(
|
|
123
|
+
createModel(
|
|
202
124
|
{ token: '', isLogin: false },
|
|
203
125
|
{
|
|
204
126
|
persist: {
|
|
205
127
|
key: 'user_v1',
|
|
206
|
-
beforeRecover: (v) => ({
|
|
207
|
-
token: v?.token ?? '',
|
|
208
|
-
isLogin: Boolean(v?.token),
|
|
209
|
-
}),
|
|
128
|
+
beforeRecover: (v) => ({ token: v?.token ?? '', isLogin: Boolean(v?.token) }),
|
|
210
129
|
},
|
|
211
130
|
}
|
|
212
131
|
);
|
|
213
132
|
```
|
|
214
133
|
|
|
215
|
-
###
|
|
134
|
+
### 初始状态用函数
|
|
216
135
|
|
|
217
136
|
```ts
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
export const appStore = createModel(() => ({ bootAt: Date.now() }));
|
|
137
|
+
createModel(() => ({ bootAt: Date.now() }));
|
|
221
138
|
```
|
|
222
139
|
|
|
223
|
-
|
|
140
|
+
---
|
|
224
141
|
|
|
225
|
-
|
|
142
|
+
## API 简表
|
|
226
143
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
144
|
+
| 方法 / 属性 | 说明 |
|
|
145
|
+
|-------------|------|
|
|
146
|
+
| `model()` / `model.getState()` / `model.use()` / `model.useState()` | 组件内 Hook,可传 selector + equalityFn |
|
|
147
|
+
| `model.setState(partial, replace?)` | 更新状态 |
|
|
148
|
+
| `model.data.state` / `model.data.getState()` | 组件外读状态 |
|
|
149
|
+
| `model.data.setState` / `model.data.subscribe` | 组件外写与订阅 |
|
|
150
|
+
| `model.reset()` / `model.destroy()` | 重置为初始值、销毁 |
|
|
231
151
|
|
|
232
|
-
|
|
152
|
+
**options**:`persist`(string 或 `{ key, storage?, debounce?, beforePersist?, beforeRecover? }`)、`logger`、`strict.forbidSetStateAfterDestroy`。
|
|
233
153
|
|
|
234
|
-
|
|
235
|
-
- 组件外(Vanilla):`model.data.state` / `model.data.getState()` / `model.data.setState()` / `model.data.subscribe()`
|
|
154
|
+
---
|
|
236
155
|
|
|
237
156
|
## License
|
|
238
157
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,61 +1,71 @@
|
|
|
1
|
-
type Listener<T> = (state: T,
|
|
2
|
-
interface StorageEngine {
|
|
3
|
-
getItem: (key: string) => string | null | Promise<string | null>;
|
|
4
|
-
setItem: (key: string, value: string) => void | Promise<void>;
|
|
5
|
-
removeItem: (key: string) => void | Promise<void>;
|
|
6
|
-
}
|
|
7
|
-
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
8
|
-
|
|
1
|
+
type Listener<T> = (state: T, prev: T) => void;
|
|
9
2
|
type Selector<T, U> = (state: T) => U;
|
|
3
|
+
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
4
|
+
type SetState<T> = (partial: T | Partial<T> | ((s: T) => T | Partial<T>), replace?: boolean) => void;
|
|
10
5
|
type ModelHook<T> = {
|
|
11
6
|
(): T;
|
|
12
|
-
<U>(
|
|
7
|
+
<U>(sel: Selector<T, U>, eq?: EqualityFn<U>): U;
|
|
8
|
+
};
|
|
9
|
+
type StorageEngine = {
|
|
10
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
11
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
12
|
+
removeItem(key: string): void | Promise<void>;
|
|
13
13
|
};
|
|
14
14
|
interface PersistOptions<T> {
|
|
15
15
|
key: string;
|
|
16
16
|
storage?: StorageEngine;
|
|
17
17
|
debounce?: number;
|
|
18
|
-
beforePersist
|
|
19
|
-
beforeRecover
|
|
20
|
-
}
|
|
21
|
-
interface Logger {
|
|
22
|
-
warn: (...args: any[]) => void;
|
|
23
|
-
error: (...args: any[]) => void;
|
|
18
|
+
beforePersist?(state: T): any;
|
|
19
|
+
beforeRecover?(value: any): T | Partial<T>;
|
|
24
20
|
}
|
|
25
21
|
interface CreateModelOptions<T> {
|
|
26
22
|
persist?: string | PersistOptions<T>;
|
|
27
|
-
logger?:
|
|
23
|
+
logger?: {
|
|
24
|
+
warn(...a: any[]): void;
|
|
25
|
+
error(...a: any[]): void;
|
|
26
|
+
};
|
|
28
27
|
strict?: {
|
|
29
28
|
forbidSetStateAfterDestroy?: boolean;
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
31
|
interface SubscribeWithSelectorOptions<U> {
|
|
33
|
-
equalityFn
|
|
32
|
+
equalityFn?(a: U, b: U): boolean;
|
|
34
33
|
fireImmediately?: boolean;
|
|
35
34
|
}
|
|
36
35
|
interface Subscribe<T> {
|
|
37
36
|
(listener: Listener<T>): () => void;
|
|
38
|
-
<U>(
|
|
37
|
+
<U>(sel: Selector<T, U>, fn: (s: U, p: U) => void, opts?: SubscribeWithSelectorOptions<U>): () => void;
|
|
39
38
|
}
|
|
40
39
|
interface ModelData<T> {
|
|
41
|
-
getState
|
|
42
|
-
setState:
|
|
40
|
+
getState(): T;
|
|
41
|
+
setState: SetState<T>;
|
|
43
42
|
subscribe: Subscribe<T>;
|
|
44
|
-
destroy
|
|
45
|
-
reset
|
|
43
|
+
destroy(): void;
|
|
44
|
+
reset(): void;
|
|
46
45
|
state: T;
|
|
47
46
|
}
|
|
48
47
|
type Model<T> = ModelHook<T> & {
|
|
49
48
|
getState: ModelHook<T>;
|
|
50
49
|
useState: ModelHook<T>;
|
|
51
|
-
setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
|
|
52
|
-
destroy: () => void;
|
|
53
|
-
reset: () => void;
|
|
54
|
-
data: ModelData<T>;
|
|
55
50
|
use: ModelHook<T>;
|
|
51
|
+
setState: SetState<T>;
|
|
52
|
+
destroy(): void;
|
|
53
|
+
reset(): void;
|
|
54
|
+
data: ModelData<T>;
|
|
55
|
+
};
|
|
56
|
+
declare function shallow<T>(a: T, b: T): boolean;
|
|
57
|
+
declare function debounce<T extends (...a: any[]) => any>(fn: T, ms: number): {
|
|
58
|
+
(...a: Parameters<T>): void;
|
|
59
|
+
flush(): void;
|
|
60
|
+
cancel(): void;
|
|
61
|
+
};
|
|
62
|
+
declare const createStore: <T>(init: T) => {
|
|
63
|
+
getState: () => T;
|
|
64
|
+
getInitialState: () => T;
|
|
65
|
+
setState(partial: T | Partial<T> | ((s: T) => T | Partial<T>), replace?: boolean): void;
|
|
66
|
+
subscribe(l: Listener<T>): () => void;
|
|
67
|
+
destroy(): void;
|
|
56
68
|
};
|
|
57
69
|
declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };
|
|
71
|
+
export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, createStore, debounce, shallow };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,61 +1,71 @@
|
|
|
1
|
-
type Listener<T> = (state: T,
|
|
2
|
-
interface StorageEngine {
|
|
3
|
-
getItem: (key: string) => string | null | Promise<string | null>;
|
|
4
|
-
setItem: (key: string, value: string) => void | Promise<void>;
|
|
5
|
-
removeItem: (key: string) => void | Promise<void>;
|
|
6
|
-
}
|
|
7
|
-
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
8
|
-
|
|
1
|
+
type Listener<T> = (state: T, prev: T) => void;
|
|
9
2
|
type Selector<T, U> = (state: T) => U;
|
|
3
|
+
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
4
|
+
type SetState<T> = (partial: T | Partial<T> | ((s: T) => T | Partial<T>), replace?: boolean) => void;
|
|
10
5
|
type ModelHook<T> = {
|
|
11
6
|
(): T;
|
|
12
|
-
<U>(
|
|
7
|
+
<U>(sel: Selector<T, U>, eq?: EqualityFn<U>): U;
|
|
8
|
+
};
|
|
9
|
+
type StorageEngine = {
|
|
10
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
11
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
12
|
+
removeItem(key: string): void | Promise<void>;
|
|
13
13
|
};
|
|
14
14
|
interface PersistOptions<T> {
|
|
15
15
|
key: string;
|
|
16
16
|
storage?: StorageEngine;
|
|
17
17
|
debounce?: number;
|
|
18
|
-
beforePersist
|
|
19
|
-
beforeRecover
|
|
20
|
-
}
|
|
21
|
-
interface Logger {
|
|
22
|
-
warn: (...args: any[]) => void;
|
|
23
|
-
error: (...args: any[]) => void;
|
|
18
|
+
beforePersist?(state: T): any;
|
|
19
|
+
beforeRecover?(value: any): T | Partial<T>;
|
|
24
20
|
}
|
|
25
21
|
interface CreateModelOptions<T> {
|
|
26
22
|
persist?: string | PersistOptions<T>;
|
|
27
|
-
logger?:
|
|
23
|
+
logger?: {
|
|
24
|
+
warn(...a: any[]): void;
|
|
25
|
+
error(...a: any[]): void;
|
|
26
|
+
};
|
|
28
27
|
strict?: {
|
|
29
28
|
forbidSetStateAfterDestroy?: boolean;
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
31
|
interface SubscribeWithSelectorOptions<U> {
|
|
33
|
-
equalityFn
|
|
32
|
+
equalityFn?(a: U, b: U): boolean;
|
|
34
33
|
fireImmediately?: boolean;
|
|
35
34
|
}
|
|
36
35
|
interface Subscribe<T> {
|
|
37
36
|
(listener: Listener<T>): () => void;
|
|
38
|
-
<U>(
|
|
37
|
+
<U>(sel: Selector<T, U>, fn: (s: U, p: U) => void, opts?: SubscribeWithSelectorOptions<U>): () => void;
|
|
39
38
|
}
|
|
40
39
|
interface ModelData<T> {
|
|
41
|
-
getState
|
|
42
|
-
setState:
|
|
40
|
+
getState(): T;
|
|
41
|
+
setState: SetState<T>;
|
|
43
42
|
subscribe: Subscribe<T>;
|
|
44
|
-
destroy
|
|
45
|
-
reset
|
|
43
|
+
destroy(): void;
|
|
44
|
+
reset(): void;
|
|
46
45
|
state: T;
|
|
47
46
|
}
|
|
48
47
|
type Model<T> = ModelHook<T> & {
|
|
49
48
|
getState: ModelHook<T>;
|
|
50
49
|
useState: ModelHook<T>;
|
|
51
|
-
setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
|
|
52
|
-
destroy: () => void;
|
|
53
|
-
reset: () => void;
|
|
54
|
-
data: ModelData<T>;
|
|
55
50
|
use: ModelHook<T>;
|
|
51
|
+
setState: SetState<T>;
|
|
52
|
+
destroy(): void;
|
|
53
|
+
reset(): void;
|
|
54
|
+
data: ModelData<T>;
|
|
55
|
+
};
|
|
56
|
+
declare function shallow<T>(a: T, b: T): boolean;
|
|
57
|
+
declare function debounce<T extends (...a: any[]) => any>(fn: T, ms: number): {
|
|
58
|
+
(...a: Parameters<T>): void;
|
|
59
|
+
flush(): void;
|
|
60
|
+
cancel(): void;
|
|
61
|
+
};
|
|
62
|
+
declare const createStore: <T>(init: T) => {
|
|
63
|
+
getState: () => T;
|
|
64
|
+
getInitialState: () => T;
|
|
65
|
+
setState(partial: T | Partial<T> | ((s: T) => T | Partial<T>), replace?: boolean): void;
|
|
66
|
+
subscribe(l: Listener<T>): () => void;
|
|
67
|
+
destroy(): void;
|
|
56
68
|
};
|
|
57
69
|
declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };
|
|
71
|
+
export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, createStore, debounce, shallow };
|
package/dist/index.js
CHANGED
|
@@ -1,254 +1 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var useSyncExternalStoreExports = require('use-sync-external-store/shim/with-selector');
|
|
4
|
-
var AsyncStorage = require('@react-native-async-storage/async-storage');
|
|
5
|
-
|
|
6
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
-
|
|
8
|
-
var useSyncExternalStoreExports__default = /*#__PURE__*/_interopDefault(useSyncExternalStoreExports);
|
|
9
|
-
var AsyncStorage__default = /*#__PURE__*/_interopDefault(AsyncStorage);
|
|
10
|
-
|
|
11
|
-
// src/createModel.ts
|
|
12
|
-
|
|
13
|
-
// src/vanilla.ts
|
|
14
|
-
function isPlainObject(value) {
|
|
15
|
-
if (Object.prototype.toString.call(value) !== "[object Object]") {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
const proto = Object.getPrototypeOf(value);
|
|
19
|
-
return proto === Object.prototype || proto === null;
|
|
20
|
-
}
|
|
21
|
-
var createStore = (initialState) => {
|
|
22
|
-
let state = initialState;
|
|
23
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
24
|
-
const setState = (partial, replace) => {
|
|
25
|
-
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
26
|
-
if (!Object.is(nextState, state)) {
|
|
27
|
-
const previousState = state;
|
|
28
|
-
const shouldReplace = replace != null ? replace : !isPlainObject(state) || !isPlainObject(nextState);
|
|
29
|
-
state = shouldReplace ? nextState : Object.assign({}, state, nextState);
|
|
30
|
-
const snapshot = new Set(listeners);
|
|
31
|
-
snapshot.forEach((listener) => listener(state, previousState));
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const getState = () => state;
|
|
35
|
-
const getInitialState = () => initialState;
|
|
36
|
-
const subscribe = (listener) => {
|
|
37
|
-
listeners.add(listener);
|
|
38
|
-
return () => listeners.delete(listener);
|
|
39
|
-
};
|
|
40
|
-
const destroy = () => {
|
|
41
|
-
listeners.clear();
|
|
42
|
-
};
|
|
43
|
-
return { getState, getInitialState, setState, subscribe, destroy };
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// src/utils.ts
|
|
47
|
-
function shallow(objA, objB) {
|
|
48
|
-
if (Object.is(objA, objB)) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const keysA = Object.keys(objA);
|
|
55
|
-
const keysB = Object.keys(objB);
|
|
56
|
-
if (keysA.length !== keysB.length) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
for (let i = 0; i < keysA.length; i++) {
|
|
60
|
-
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
function debounce(func, wait) {
|
|
67
|
-
let timeout;
|
|
68
|
-
let lastArgs;
|
|
69
|
-
let context;
|
|
70
|
-
const debounced = function(...args) {
|
|
71
|
-
context = this;
|
|
72
|
-
lastArgs = args;
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
timeout = setTimeout(() => {
|
|
75
|
-
func.apply(context, args);
|
|
76
|
-
lastArgs = void 0;
|
|
77
|
-
timeout = void 0;
|
|
78
|
-
}, wait);
|
|
79
|
-
};
|
|
80
|
-
debounced.flush = () => {
|
|
81
|
-
if (timeout && lastArgs) {
|
|
82
|
-
clearTimeout(timeout);
|
|
83
|
-
func.apply(context, lastArgs);
|
|
84
|
-
lastArgs = void 0;
|
|
85
|
-
timeout = void 0;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
debounced.cancel = () => {
|
|
89
|
-
clearTimeout(timeout);
|
|
90
|
-
lastArgs = void 0;
|
|
91
|
-
timeout = void 0;
|
|
92
|
-
};
|
|
93
|
-
return debounced;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/createModel.ts
|
|
97
|
-
var { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports__default.default;
|
|
98
|
-
var identity = (v) => v;
|
|
99
|
-
function isInvalidHookCallError(error) {
|
|
100
|
-
return error instanceof Error && /Invalid hook call|Hooks can only be called/i.test(error.message);
|
|
101
|
-
}
|
|
102
|
-
function createModel(initialState, options) {
|
|
103
|
-
var _a;
|
|
104
|
-
const initialValue = typeof initialState === "function" ? initialState() : initialState;
|
|
105
|
-
const store = createStore(initialValue);
|
|
106
|
-
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : console;
|
|
107
|
-
setupModelSideEffects(store, options, logger);
|
|
108
|
-
let isDestroyed = false;
|
|
109
|
-
const setState = (partial, replace) => {
|
|
110
|
-
var _a2;
|
|
111
|
-
if (isDestroyed && ((_a2 = options == null ? void 0 : options.strict) == null ? void 0 : _a2.forbidSetStateAfterDestroy)) {
|
|
112
|
-
const error = new Error("[react-native-hox] model \u5DF2\u9500\u6BC1\uFF0C\u65E0\u6CD5\u518D\u8C03\u7528 setState\u3002");
|
|
113
|
-
logger.error(error);
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
store.setState(partial, replace);
|
|
117
|
-
};
|
|
118
|
-
const destroy = () => {
|
|
119
|
-
isDestroyed = true;
|
|
120
|
-
store.destroy();
|
|
121
|
-
};
|
|
122
|
-
const reset = () => setState(initialValue, true);
|
|
123
|
-
const subscribe = createSubscribe(store);
|
|
124
|
-
const data = {
|
|
125
|
-
getState: store.getState,
|
|
126
|
-
setState,
|
|
127
|
-
subscribe,
|
|
128
|
-
destroy,
|
|
129
|
-
reset
|
|
130
|
-
};
|
|
131
|
-
Object.defineProperty(data, "state", {
|
|
132
|
-
get: () => store.getState(),
|
|
133
|
-
enumerable: true
|
|
134
|
-
});
|
|
135
|
-
const hook = createModelHook(store);
|
|
136
|
-
const model = {
|
|
137
|
-
getState: hook,
|
|
138
|
-
useState: hook,
|
|
139
|
-
use: hook,
|
|
140
|
-
// Add 'use' alias
|
|
141
|
-
setState,
|
|
142
|
-
reset,
|
|
143
|
-
destroy,
|
|
144
|
-
data
|
|
145
|
-
};
|
|
146
|
-
const modelFn = function(selector, equalityFn) {
|
|
147
|
-
return hook(selector, equalityFn);
|
|
148
|
-
};
|
|
149
|
-
Object.assign(modelFn, model);
|
|
150
|
-
return modelFn;
|
|
151
|
-
}
|
|
152
|
-
function createSubscribe(store) {
|
|
153
|
-
return ((arg1, arg2, arg3) => {
|
|
154
|
-
var _a;
|
|
155
|
-
if (typeof arg1 === "function" && typeof arg2 !== "function") {
|
|
156
|
-
return store.subscribe(arg1);
|
|
157
|
-
}
|
|
158
|
-
const selector = arg1;
|
|
159
|
-
const listener = arg2;
|
|
160
|
-
const options = arg3 != null ? arg3 : {};
|
|
161
|
-
const equalityFn = (_a = options.equalityFn) != null ? _a : Object.is;
|
|
162
|
-
let lastSelected = selector(store.getState());
|
|
163
|
-
if (options.fireImmediately) {
|
|
164
|
-
listener(lastSelected, lastSelected);
|
|
165
|
-
}
|
|
166
|
-
return store.subscribe((nextState) => {
|
|
167
|
-
const nextSelected = selector(nextState);
|
|
168
|
-
if (equalityFn(lastSelected, nextSelected)) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
const prevSelected = lastSelected;
|
|
172
|
-
lastSelected = nextSelected;
|
|
173
|
-
listener(nextSelected, prevSelected);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
function createModelHook(store) {
|
|
178
|
-
const useModel = ((selector, equalityFn = shallow) => {
|
|
179
|
-
try {
|
|
180
|
-
return useSyncExternalStoreWithSelector(
|
|
181
|
-
store.subscribe,
|
|
182
|
-
store.getState,
|
|
183
|
-
store.getInitialState,
|
|
184
|
-
selector != null ? selector : identity,
|
|
185
|
-
equalityFn
|
|
186
|
-
);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (isInvalidHookCallError(error)) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
"[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"
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
throw error;
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
return useModel;
|
|
197
|
-
}
|
|
198
|
-
function setupModelSideEffects(store, options, logger) {
|
|
199
|
-
var _a, _b, _c, _d;
|
|
200
|
-
const persist = typeof (options == null ? void 0 : options.persist) === "string" ? { key: options.persist } : options == null ? void 0 : options.persist;
|
|
201
|
-
const persistKey = persist == null ? void 0 : persist.key;
|
|
202
|
-
const storage = (_a = persist == null ? void 0 : persist.storage) != null ? _a : AsyncStorage__default.default;
|
|
203
|
-
if (!persistKey) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const beforePersist = (_b = persist == null ? void 0 : persist.beforePersist) != null ? _b : ((s) => s);
|
|
207
|
-
const beforeRecover = (_c = persist == null ? void 0 : persist.beforeRecover) != null ? _c : ((v) => v);
|
|
208
|
-
const debounceMs = (_d = persist == null ? void 0 : persist.debounce) != null ? _d : 100;
|
|
209
|
-
if (!storage) {
|
|
210
|
-
logger.warn(
|
|
211
|
-
"[react-native-hox] persist \u5DF2\u5F00\u542F\u4F46 storage \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 persist.storage \u4F20\u5165 AsyncStorage/MMKV \u7B49\u3002"
|
|
212
|
-
);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
let isDestroyed = false;
|
|
216
|
-
Promise.resolve().then(() => storage.getItem(persistKey)).then((val) => {
|
|
217
|
-
if (isDestroyed) return;
|
|
218
|
-
if (!val) return;
|
|
219
|
-
try {
|
|
220
|
-
const parsed = JSON.parse(val);
|
|
221
|
-
const recovered = beforeRecover(parsed);
|
|
222
|
-
store.setState(recovered, false);
|
|
223
|
-
} catch (e) {
|
|
224
|
-
logger.error("[react-native-hox] Failed to parse stored value:", e);
|
|
225
|
-
}
|
|
226
|
-
}).catch((e) => {
|
|
227
|
-
logger.error("[react-native-hox] Recover failed:", e);
|
|
228
|
-
});
|
|
229
|
-
const save = debounce((state) => {
|
|
230
|
-
if (isDestroyed) return;
|
|
231
|
-
try {
|
|
232
|
-
const prepared = beforePersist(state);
|
|
233
|
-
const result = storage.setItem(persistKey, JSON.stringify(prepared));
|
|
234
|
-
Promise.resolve(result).catch((e) => {
|
|
235
|
-
logger.error("[react-native-hox] Save failed:", e);
|
|
236
|
-
});
|
|
237
|
-
} catch (e) {
|
|
238
|
-
logger.error("[react-native-hox] Save failed:", e);
|
|
239
|
-
}
|
|
240
|
-
}, debounceMs);
|
|
241
|
-
const unsubscribe = store.subscribe((state) => {
|
|
242
|
-
save(state);
|
|
243
|
-
});
|
|
244
|
-
const originalDestroy = store.destroy;
|
|
245
|
-
store.destroy = () => {
|
|
246
|
-
unsubscribe();
|
|
247
|
-
save.flush();
|
|
248
|
-
isDestroyed = true;
|
|
249
|
-
originalDestroy();
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
exports.createModel = createModel;
|
|
254
|
-
exports.shallow = shallow;
|
|
1
|
+
'use strict';var E=require('use-sync-external-store/shim/with-selector'),w=require('@react-native-async-storage/async-storage');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var E__default=/*#__PURE__*/_interopDefault(E);var w__default=/*#__PURE__*/_interopDefault(w);var I=E__default.default.useSyncExternalStoreWithSelector,M=o=>{if(Object.prototype.toString.call(o)!=="[object Object]")return false;let e=Object.getPrototypeOf(o);return e===Object.prototype||e===null};function H(o,e){if(Object.is(o,e))return true;if(typeof o!="object"||!o||typeof e!="object"||!e)return false;let r=Object.keys(o);return r.length===Object.keys(e).length&&r.every(t=>Object.prototype.hasOwnProperty.call(e,t)&&Object.is(o[t],e[t]))}function q(o,e){let r,t,n=function(...l){t=l,clearTimeout(r),r=setTimeout(()=>{o(...l),t=r=void 0;},e);};return n.flush=()=>{r&&t&&(clearTimeout(r),o(...t),t=r=void 0);},n.cancel=()=>{clearTimeout(r),t=r=void 0;},n}var F=o=>{let e=o,r=new Set;return {getState:()=>e,getInitialState:()=>o,setState(t,n){let l=typeof t=="function"?t(e):t;if(!Object.is(l,e)){let f=e;e=(n!=null?n:!M(e)||!M(l))?l:{...e,...l},new Set(r).forEach(S=>S(e,f));}},subscribe(t){return r.add(t),()=>{r.delete(t);}},destroy(){r.clear();}}};function L(o,e){var h,O,k,x,P;let r=typeof o=="function"?o():o,t=F(r),n=(h=e==null?void 0:e.logger)!=null?h:console,l=false,f,S=(s,c)=>{var i;if(l&&((i=e==null?void 0:e.strict)!=null&&i.forbidSetStateAfterDestroy)){let u=new Error("[react-native-hox] model \u5DF2\u9500\u6BC1\uFF0C\u65E0\u6CD5\u518D\u8C03\u7528 setState\u3002");throw n.error(u),u}t.setState(s,c);},d=typeof(e==null?void 0:e.persist)=="string"?{key:e.persist}:e==null?void 0:e.persist;if(d!=null&&d.key){let s=(O=d.storage)!=null?O:w__default.default;if(s){let c=(k=d.beforePersist)!=null?k:(a=>a),i=(x=d.beforeRecover)!=null?x:(a=>a),u=false;Promise.resolve().then(()=>s.getItem(d.key)).then(a=>{if(!(u||!a))try{t.setState(i(JSON.parse(a)),!1);}catch(y){n.error("[react-native-hox] Failed to parse stored value:",y);}}).catch(a=>n.error("[react-native-hox] Recover failed:",a));let T=q(a=>{if(!u)try{Promise.resolve(s.setItem(d.key,JSON.stringify(c(a)))).catch(y=>n.error("[react-native-hox] Save failed:",y));}catch(y){n.error("[react-native-hox] Save failed:",y);}},(P=d.debounce)!=null?P:100),g=t.subscribe(a=>T(a));f=()=>{g(),T.flush(),u=true;};}else n.warn("[react-native-hox] persist \u5DF2\u5F00\u542F\u4F46 storage \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 persist.storage \u4F20\u5165 AsyncStorage/MMKV \u7B49\u3002");}let v=()=>{f==null||f(),l=true,t.destroy();},p=()=>S(r,true),U=((s,c,i)=>{if(typeof s=="function"&&typeof c!="function")return t.subscribe(s);let u=i!=null?i:{},T=s(t.getState());return u.fireImmediately&&c(T,T),t.subscribe(g=>{var y;let a=s(g);if(!((y=u.equalityFn)!=null?y:Object.is)(T,a)){let j=T;T=a,c(a,j);}})}),b=((s,c=H)=>{try{return I(t.subscribe,t.getState,t.getInitialState,s!=null?s:(i=>i),c)}catch(i){throw i instanceof Error&&/Invalid hook call|Hooks can only be called/i.test(i.message)?new Error("[react-native-hox] \u7EC4\u4EF6\u5916\u8BF7\u4F7F\u7528 model.data.getState() / model.data.state"):i}}),m={getState:t.getState,setState:S,subscribe:U,destroy:v,reset:p};return Object.defineProperty(m,"state",{get:t.getState,enumerable:true}),Object.assign((s,c)=>b(s,c),{getState:b,useState:b,use:b,setState:S,destroy:v,reset:p,data:m})}exports.createModel=L;exports.createStore=F;exports.debounce=q;exports.shallow=H;
|
package/dist/index.mjs
CHANGED
|
@@ -1,246 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
|
-
|
|
4
|
-
// src/createModel.ts
|
|
5
|
-
|
|
6
|
-
// src/vanilla.ts
|
|
7
|
-
function isPlainObject(value) {
|
|
8
|
-
if (Object.prototype.toString.call(value) !== "[object Object]") {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
const proto = Object.getPrototypeOf(value);
|
|
12
|
-
return proto === Object.prototype || proto === null;
|
|
13
|
-
}
|
|
14
|
-
var createStore = (initialState) => {
|
|
15
|
-
let state = initialState;
|
|
16
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
17
|
-
const setState = (partial, replace) => {
|
|
18
|
-
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
19
|
-
if (!Object.is(nextState, state)) {
|
|
20
|
-
const previousState = state;
|
|
21
|
-
const shouldReplace = replace != null ? replace : !isPlainObject(state) || !isPlainObject(nextState);
|
|
22
|
-
state = shouldReplace ? nextState : Object.assign({}, state, nextState);
|
|
23
|
-
const snapshot = new Set(listeners);
|
|
24
|
-
snapshot.forEach((listener) => listener(state, previousState));
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
const getState = () => state;
|
|
28
|
-
const getInitialState = () => initialState;
|
|
29
|
-
const subscribe = (listener) => {
|
|
30
|
-
listeners.add(listener);
|
|
31
|
-
return () => listeners.delete(listener);
|
|
32
|
-
};
|
|
33
|
-
const destroy = () => {
|
|
34
|
-
listeners.clear();
|
|
35
|
-
};
|
|
36
|
-
return { getState, getInitialState, setState, subscribe, destroy };
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// src/utils.ts
|
|
40
|
-
function shallow(objA, objB) {
|
|
41
|
-
if (Object.is(objA, objB)) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
const keysA = Object.keys(objA);
|
|
48
|
-
const keysB = Object.keys(objB);
|
|
49
|
-
if (keysA.length !== keysB.length) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
for (let i = 0; i < keysA.length; i++) {
|
|
53
|
-
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
function debounce(func, wait) {
|
|
60
|
-
let timeout;
|
|
61
|
-
let lastArgs;
|
|
62
|
-
let context;
|
|
63
|
-
const debounced = function(...args) {
|
|
64
|
-
context = this;
|
|
65
|
-
lastArgs = args;
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
timeout = setTimeout(() => {
|
|
68
|
-
func.apply(context, args);
|
|
69
|
-
lastArgs = void 0;
|
|
70
|
-
timeout = void 0;
|
|
71
|
-
}, wait);
|
|
72
|
-
};
|
|
73
|
-
debounced.flush = () => {
|
|
74
|
-
if (timeout && lastArgs) {
|
|
75
|
-
clearTimeout(timeout);
|
|
76
|
-
func.apply(context, lastArgs);
|
|
77
|
-
lastArgs = void 0;
|
|
78
|
-
timeout = void 0;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
debounced.cancel = () => {
|
|
82
|
-
clearTimeout(timeout);
|
|
83
|
-
lastArgs = void 0;
|
|
84
|
-
timeout = void 0;
|
|
85
|
-
};
|
|
86
|
-
return debounced;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/createModel.ts
|
|
90
|
-
var { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports;
|
|
91
|
-
var identity = (v) => v;
|
|
92
|
-
function isInvalidHookCallError(error) {
|
|
93
|
-
return error instanceof Error && /Invalid hook call|Hooks can only be called/i.test(error.message);
|
|
94
|
-
}
|
|
95
|
-
function createModel(initialState, options) {
|
|
96
|
-
var _a;
|
|
97
|
-
const initialValue = typeof initialState === "function" ? initialState() : initialState;
|
|
98
|
-
const store = createStore(initialValue);
|
|
99
|
-
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : console;
|
|
100
|
-
setupModelSideEffects(store, options, logger);
|
|
101
|
-
let isDestroyed = false;
|
|
102
|
-
const setState = (partial, replace) => {
|
|
103
|
-
var _a2;
|
|
104
|
-
if (isDestroyed && ((_a2 = options == null ? void 0 : options.strict) == null ? void 0 : _a2.forbidSetStateAfterDestroy)) {
|
|
105
|
-
const error = new Error("[react-native-hox] model \u5DF2\u9500\u6BC1\uFF0C\u65E0\u6CD5\u518D\u8C03\u7528 setState\u3002");
|
|
106
|
-
logger.error(error);
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
store.setState(partial, replace);
|
|
110
|
-
};
|
|
111
|
-
const destroy = () => {
|
|
112
|
-
isDestroyed = true;
|
|
113
|
-
store.destroy();
|
|
114
|
-
};
|
|
115
|
-
const reset = () => setState(initialValue, true);
|
|
116
|
-
const subscribe = createSubscribe(store);
|
|
117
|
-
const data = {
|
|
118
|
-
getState: store.getState,
|
|
119
|
-
setState,
|
|
120
|
-
subscribe,
|
|
121
|
-
destroy,
|
|
122
|
-
reset
|
|
123
|
-
};
|
|
124
|
-
Object.defineProperty(data, "state", {
|
|
125
|
-
get: () => store.getState(),
|
|
126
|
-
enumerable: true
|
|
127
|
-
});
|
|
128
|
-
const hook = createModelHook(store);
|
|
129
|
-
const model = {
|
|
130
|
-
getState: hook,
|
|
131
|
-
useState: hook,
|
|
132
|
-
use: hook,
|
|
133
|
-
// Add 'use' alias
|
|
134
|
-
setState,
|
|
135
|
-
reset,
|
|
136
|
-
destroy,
|
|
137
|
-
data
|
|
138
|
-
};
|
|
139
|
-
const modelFn = function(selector, equalityFn) {
|
|
140
|
-
return hook(selector, equalityFn);
|
|
141
|
-
};
|
|
142
|
-
Object.assign(modelFn, model);
|
|
143
|
-
return modelFn;
|
|
144
|
-
}
|
|
145
|
-
function createSubscribe(store) {
|
|
146
|
-
return ((arg1, arg2, arg3) => {
|
|
147
|
-
var _a;
|
|
148
|
-
if (typeof arg1 === "function" && typeof arg2 !== "function") {
|
|
149
|
-
return store.subscribe(arg1);
|
|
150
|
-
}
|
|
151
|
-
const selector = arg1;
|
|
152
|
-
const listener = arg2;
|
|
153
|
-
const options = arg3 != null ? arg3 : {};
|
|
154
|
-
const equalityFn = (_a = options.equalityFn) != null ? _a : Object.is;
|
|
155
|
-
let lastSelected = selector(store.getState());
|
|
156
|
-
if (options.fireImmediately) {
|
|
157
|
-
listener(lastSelected, lastSelected);
|
|
158
|
-
}
|
|
159
|
-
return store.subscribe((nextState) => {
|
|
160
|
-
const nextSelected = selector(nextState);
|
|
161
|
-
if (equalityFn(lastSelected, nextSelected)) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const prevSelected = lastSelected;
|
|
165
|
-
lastSelected = nextSelected;
|
|
166
|
-
listener(nextSelected, prevSelected);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
function createModelHook(store) {
|
|
171
|
-
const useModel = ((selector, equalityFn = shallow) => {
|
|
172
|
-
try {
|
|
173
|
-
return useSyncExternalStoreWithSelector(
|
|
174
|
-
store.subscribe,
|
|
175
|
-
store.getState,
|
|
176
|
-
store.getInitialState,
|
|
177
|
-
selector != null ? selector : identity,
|
|
178
|
-
equalityFn
|
|
179
|
-
);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (isInvalidHookCallError(error)) {
|
|
182
|
-
throw new Error(
|
|
183
|
-
"[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"
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
return useModel;
|
|
190
|
-
}
|
|
191
|
-
function setupModelSideEffects(store, options, logger) {
|
|
192
|
-
var _a, _b, _c, _d;
|
|
193
|
-
const persist = typeof (options == null ? void 0 : options.persist) === "string" ? { key: options.persist } : options == null ? void 0 : options.persist;
|
|
194
|
-
const persistKey = persist == null ? void 0 : persist.key;
|
|
195
|
-
const storage = (_a = persist == null ? void 0 : persist.storage) != null ? _a : AsyncStorage;
|
|
196
|
-
if (!persistKey) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const beforePersist = (_b = persist == null ? void 0 : persist.beforePersist) != null ? _b : ((s) => s);
|
|
200
|
-
const beforeRecover = (_c = persist == null ? void 0 : persist.beforeRecover) != null ? _c : ((v) => v);
|
|
201
|
-
const debounceMs = (_d = persist == null ? void 0 : persist.debounce) != null ? _d : 100;
|
|
202
|
-
if (!storage) {
|
|
203
|
-
logger.warn(
|
|
204
|
-
"[react-native-hox] persist \u5DF2\u5F00\u542F\u4F46 storage \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 persist.storage \u4F20\u5165 AsyncStorage/MMKV \u7B49\u3002"
|
|
205
|
-
);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
let isDestroyed = false;
|
|
209
|
-
Promise.resolve().then(() => storage.getItem(persistKey)).then((val) => {
|
|
210
|
-
if (isDestroyed) return;
|
|
211
|
-
if (!val) return;
|
|
212
|
-
try {
|
|
213
|
-
const parsed = JSON.parse(val);
|
|
214
|
-
const recovered = beforeRecover(parsed);
|
|
215
|
-
store.setState(recovered, false);
|
|
216
|
-
} catch (e) {
|
|
217
|
-
logger.error("[react-native-hox] Failed to parse stored value:", e);
|
|
218
|
-
}
|
|
219
|
-
}).catch((e) => {
|
|
220
|
-
logger.error("[react-native-hox] Recover failed:", e);
|
|
221
|
-
});
|
|
222
|
-
const save = debounce((state) => {
|
|
223
|
-
if (isDestroyed) return;
|
|
224
|
-
try {
|
|
225
|
-
const prepared = beforePersist(state);
|
|
226
|
-
const result = storage.setItem(persistKey, JSON.stringify(prepared));
|
|
227
|
-
Promise.resolve(result).catch((e) => {
|
|
228
|
-
logger.error("[react-native-hox] Save failed:", e);
|
|
229
|
-
});
|
|
230
|
-
} catch (e) {
|
|
231
|
-
logger.error("[react-native-hox] Save failed:", e);
|
|
232
|
-
}
|
|
233
|
-
}, debounceMs);
|
|
234
|
-
const unsubscribe = store.subscribe((state) => {
|
|
235
|
-
save(state);
|
|
236
|
-
});
|
|
237
|
-
const originalDestroy = store.destroy;
|
|
238
|
-
store.destroy = () => {
|
|
239
|
-
unsubscribe();
|
|
240
|
-
save.flush();
|
|
241
|
-
isDestroyed = true;
|
|
242
|
-
originalDestroy();
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export { createModel, shallow };
|
|
1
|
+
import E from'use-sync-external-store/shim/with-selector';import w from'@react-native-async-storage/async-storage';var I=E.useSyncExternalStoreWithSelector,M=o=>{if(Object.prototype.toString.call(o)!=="[object Object]")return false;let e=Object.getPrototypeOf(o);return e===Object.prototype||e===null};function H(o,e){if(Object.is(o,e))return true;if(typeof o!="object"||!o||typeof e!="object"||!e)return false;let r=Object.keys(o);return r.length===Object.keys(e).length&&r.every(t=>Object.prototype.hasOwnProperty.call(e,t)&&Object.is(o[t],e[t]))}function q(o,e){let r,t,n=function(...l){t=l,clearTimeout(r),r=setTimeout(()=>{o(...l),t=r=void 0;},e);};return n.flush=()=>{r&&t&&(clearTimeout(r),o(...t),t=r=void 0);},n.cancel=()=>{clearTimeout(r),t=r=void 0;},n}var F=o=>{let e=o,r=new Set;return {getState:()=>e,getInitialState:()=>o,setState(t,n){let l=typeof t=="function"?t(e):t;if(!Object.is(l,e)){let f=e;e=(n!=null?n:!M(e)||!M(l))?l:{...e,...l},new Set(r).forEach(S=>S(e,f));}},subscribe(t){return r.add(t),()=>{r.delete(t);}},destroy(){r.clear();}}};function L(o,e){var h,O,k,x,P;let r=typeof o=="function"?o():o,t=F(r),n=(h=e==null?void 0:e.logger)!=null?h:console,l=false,f,S=(s,c)=>{var i;if(l&&((i=e==null?void 0:e.strict)!=null&&i.forbidSetStateAfterDestroy)){let u=new Error("[react-native-hox] model \u5DF2\u9500\u6BC1\uFF0C\u65E0\u6CD5\u518D\u8C03\u7528 setState\u3002");throw n.error(u),u}t.setState(s,c);},d=typeof(e==null?void 0:e.persist)=="string"?{key:e.persist}:e==null?void 0:e.persist;if(d!=null&&d.key){let s=(O=d.storage)!=null?O:w;if(s){let c=(k=d.beforePersist)!=null?k:(a=>a),i=(x=d.beforeRecover)!=null?x:(a=>a),u=false;Promise.resolve().then(()=>s.getItem(d.key)).then(a=>{if(!(u||!a))try{t.setState(i(JSON.parse(a)),!1);}catch(y){n.error("[react-native-hox] Failed to parse stored value:",y);}}).catch(a=>n.error("[react-native-hox] Recover failed:",a));let T=q(a=>{if(!u)try{Promise.resolve(s.setItem(d.key,JSON.stringify(c(a)))).catch(y=>n.error("[react-native-hox] Save failed:",y));}catch(y){n.error("[react-native-hox] Save failed:",y);}},(P=d.debounce)!=null?P:100),g=t.subscribe(a=>T(a));f=()=>{g(),T.flush(),u=true;};}else n.warn("[react-native-hox] persist \u5DF2\u5F00\u542F\u4F46 storage \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 persist.storage \u4F20\u5165 AsyncStorage/MMKV \u7B49\u3002");}let v=()=>{f==null||f(),l=true,t.destroy();},p=()=>S(r,true),U=((s,c,i)=>{if(typeof s=="function"&&typeof c!="function")return t.subscribe(s);let u=i!=null?i:{},T=s(t.getState());return u.fireImmediately&&c(T,T),t.subscribe(g=>{var y;let a=s(g);if(!((y=u.equalityFn)!=null?y:Object.is)(T,a)){let j=T;T=a,c(a,j);}})}),b=((s,c=H)=>{try{return I(t.subscribe,t.getState,t.getInitialState,s!=null?s:(i=>i),c)}catch(i){throw i instanceof Error&&/Invalid hook call|Hooks can only be called/i.test(i.message)?new Error("[react-native-hox] \u7EC4\u4EF6\u5916\u8BF7\u4F7F\u7528 model.data.getState() / model.data.state"):i}}),m={getState:t.getState,setState:S,subscribe:U,destroy:v,reset:p};return Object.defineProperty(m,"state",{get:t.getState,enumerable:true}),Object.assign((s,c)=>b(s,c),{getState:b,useState:b,use:b,setState:S,destroy:v,reset:p,data:m})}export{L as createModel,F as createStore,q as debounce,H as shallow};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-hox",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "一个轻量级、类型安全、零心智负担的 React Native 状态管理解决方案。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://gitee.com/ws18250840411/react-native-hox#readme",
|
|
22
22
|
"license": "ISC",
|
|
23
|
-
"author": "
|
|
23
|
+
"author": "wangwenshan",
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"main": "dist/index.js",
|
|
26
26
|
"module": "dist/index.mjs",
|