react-native-hox 0.0.3 → 1.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 +62 -141
- 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 +4 -4
package/README.md
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
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
|
### 安装
|
|
12
27
|
|
|
13
28
|
```bash
|
|
14
|
-
npm i react-native-hox
|
|
29
|
+
npm i react-native-hox @react-native-async-storage/async-storage
|
|
15
30
|
```
|
|
16
31
|
|
|
32
|
+
> 项目中已有 `@react-native-async-storage/async-storage` 则无需重复安装。
|
|
33
|
+
|
|
17
34
|
### 最小示例
|
|
18
35
|
|
|
19
36
|
```ts
|
|
20
37
|
// stores/user.ts
|
|
21
38
|
import { createModel } from 'react-native-hox';
|
|
22
39
|
|
|
23
|
-
export const userStore = createModel({ name: 'Guest' });
|
|
24
|
-
//
|
|
40
|
+
export const userStore = createModel({ name: 'Guest' });
|
|
41
|
+
// 持久化:createModel({ name: 'Guest' }, { persist: 'user_v1' });
|
|
25
42
|
```
|
|
26
43
|
|
|
27
44
|
```tsx
|
|
@@ -41,198 +58,102 @@ export function Profile() {
|
|
|
41
58
|
}
|
|
42
59
|
```
|
|
43
60
|
|
|
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';
|
|
61
|
+
---
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
const profile = profileStore.getState();
|
|
63
|
+
## 功能概览
|
|
61
64
|
|
|
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:只在关注的数据变化时重渲染
|
|
65
|
+
### 1) 更新状态:自动合并 / 显式替换
|
|
74
66
|
|
|
75
67
|
```ts
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export const counterStore = createModel({ count: 0, text: 'a' });
|
|
68
|
+
const profileStore = createModel({ name: 'Guest', role: 'user' as 'user' | 'admin' });
|
|
79
69
|
```
|
|
80
70
|
|
|
81
71
|
```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
|
-
}
|
|
72
|
+
profileStore.setState({ name: 'Alice' }); // 合并
|
|
73
|
+
profileStore.setState({ name: 'Bob', role: 'admin' }, true); // 整体替换
|
|
97
74
|
```
|
|
98
75
|
|
|
99
|
-
|
|
76
|
+
### 2) Selector:只在关注数据变化时重渲染
|
|
100
77
|
|
|
101
78
|
```tsx
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
79
|
+
const count = counterStore.getState((s) => s.count);
|
|
80
|
+
// 返回对象时建议用 shallow
|
|
105
81
|
const picked = counterStore.getState((s) => ({ count: s.count }), shallow);
|
|
106
82
|
```
|
|
107
83
|
|
|
108
84
|
### 3) 组件外读写:Vanilla API
|
|
109
85
|
|
|
110
86
|
```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();
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 4) 持久化:persist(默认 AsyncStorage)
|
|
123
|
-
|
|
124
|
-
只要提供一个 key 即可开启持久化(默认使用 `@react-native-async-storage/async-storage`,无需重复传 `storage`):
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import { createModel } from 'react-native-hox';
|
|
128
|
-
|
|
129
|
-
export const authStore = createModel({ token: '' }, { persist: 'auth_v1' });
|
|
87
|
+
const name = userStore.data.state.name;
|
|
88
|
+
userStore.data.getState();
|
|
89
|
+
userStore.data.subscribe((next, prev) => {});
|
|
130
90
|
```
|
|
131
91
|
|
|
132
|
-
|
|
92
|
+
### 4) 持久化
|
|
133
93
|
|
|
134
94
|
```ts
|
|
135
|
-
|
|
136
|
-
|
|
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 } });
|
|
95
|
+
createModel({ token: '' }, { persist: 'auth_v1' });
|
|
96
|
+
// 或自定义 storage / beforeRecover / beforePersist
|
|
97
|
+
createModel({ token: '' }, { persist: { key: 'auth_v1', storage: myStorage } });
|
|
145
98
|
```
|
|
146
99
|
|
|
147
100
|
### 5) reset / destroy / strict
|
|
148
101
|
|
|
149
102
|
```ts
|
|
150
|
-
import { userStore } from './stores/user';
|
|
151
|
-
|
|
152
103
|
userStore.reset();
|
|
153
104
|
userStore.destroy();
|
|
105
|
+
createModel({ count: 0 }, { strict: { forbidSetStateAfterDestroy: true } });
|
|
154
106
|
```
|
|
155
107
|
|
|
156
|
-
|
|
157
|
-
import { createModel } from 'react-native-hox';
|
|
108
|
+
---
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
{ count: 0 },
|
|
161
|
-
{ strict: { forbidSetStateAfterDestroy: true } }
|
|
162
|
-
);
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## 进阶使用
|
|
110
|
+
## 进阶
|
|
166
111
|
|
|
167
|
-
###
|
|
112
|
+
### subscribe(selector):按 slice 订阅
|
|
168
113
|
|
|
169
114
|
```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
|
-
}
|
|
115
|
+
userStore.data.subscribe(
|
|
116
|
+
(s) => s.name,
|
|
117
|
+
(next, prev) => console.log(next, prev),
|
|
118
|
+
{ fireImmediately: true }
|
|
119
|
+
);
|
|
194
120
|
```
|
|
195
121
|
|
|
196
|
-
###
|
|
122
|
+
### 持久化迁移
|
|
197
123
|
|
|
198
124
|
```ts
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
export const userPersistStore = createModel(
|
|
125
|
+
createModel(
|
|
202
126
|
{ token: '', isLogin: false },
|
|
203
127
|
{
|
|
204
128
|
persist: {
|
|
205
129
|
key: 'user_v1',
|
|
206
|
-
beforeRecover: (v) => ({
|
|
207
|
-
token: v?.token ?? '',
|
|
208
|
-
isLogin: Boolean(v?.token),
|
|
209
|
-
}),
|
|
130
|
+
beforeRecover: (v) => ({ token: v?.token ?? '', isLogin: Boolean(v?.token) }),
|
|
210
131
|
},
|
|
211
132
|
}
|
|
212
133
|
);
|
|
213
134
|
```
|
|
214
135
|
|
|
215
|
-
###
|
|
136
|
+
### 初始状态用函数
|
|
216
137
|
|
|
217
138
|
```ts
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
export const appStore = createModel(() => ({ bootAt: Date.now() }));
|
|
139
|
+
createModel(() => ({ bootAt: Date.now() }));
|
|
221
140
|
```
|
|
222
141
|
|
|
223
|
-
|
|
142
|
+
---
|
|
224
143
|
|
|
225
|
-
|
|
144
|
+
## API 简表
|
|
226
145
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
146
|
+
| 方法 / 属性 | 说明 |
|
|
147
|
+
|-------------|------|
|
|
148
|
+
| `model()` / `model.getState()` / `model.use()` / `model.useState()` | 组件内 Hook,可传 selector + equalityFn |
|
|
149
|
+
| `model.setState(partial, replace?)` | 更新状态 |
|
|
150
|
+
| `model.data.state` / `model.data.getState()` | 组件外读状态 |
|
|
151
|
+
| `model.data.setState` / `model.data.subscribe` | 组件外写与订阅 |
|
|
152
|
+
| `model.reset()` / `model.destroy()` | 重置为初始值、销毁 |
|
|
231
153
|
|
|
232
|
-
|
|
154
|
+
**options**:`persist`(string 或 `{ key, storage?, debounce?, beforePersist?, beforeRecover? }`)、`logger`、`strict.forbidSetStateAfterDestroy`。
|
|
233
155
|
|
|
234
|
-
|
|
235
|
-
- 组件外(Vanilla):`model.data.state` / `model.data.getState()` / `model.data.setState()` / `model.data.subscribe()`
|
|
156
|
+
---
|
|
236
157
|
|
|
237
158
|
## License
|
|
238
159
|
|
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": "
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"prepack": "npm run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
50
49
|
"use-sync-external-store": "^1.6.0"
|
|
51
50
|
},
|
|
52
51
|
"peerDependencies": {
|
|
53
52
|
"react": ">=16.8",
|
|
54
|
-
"react-native": ">=0.65"
|
|
53
|
+
"react-native": ">=0.65",
|
|
54
|
+
"@react-native-async-storage/async-storage": ">=2.0.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/jest": "^30.0.0",
|