react-native-hox 0.0.1-beta → 0.0.2

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 CHANGED
@@ -1,139 +1,239 @@
1
1
  # react-native-hox
2
2
 
3
- **为复杂业务场景而生的状态管理解决方案**。
3
+ 面向 React Native 的轻量状态管理方案:类型安全、API 极简,支持组件内 Hook 与组件外 Vanilla 读写,并内置可选持久化能力。
4
4
 
5
- 我们把 API 收敛到一个:`createModel`。组件内像 Hook 一样用,组件外(如网络请求拦截器)也能直接读取/订阅最新状态。
5
+ [![NPM Version](https://img.shields.io/npm/v/react-native-hox)](https://www.npmjs.com/package/react-native-hox)
6
+ [![TypeScript](https://img.shields.io/badge/language-TypeScript-blue)](https://www.typescriptlang.org/)
7
+ [![License](https://img.shields.io/npm/l/react-native-hox)](https://www.npmjs.com/package/react-native-hox)
6
8
 
7
- ---
9
+ ## 快速开始
8
10
 
9
- ## 核心特性
11
+ ### 安装
10
12
 
11
- * **一个 API**:`createModel` 创建全局模型。
12
- * **组件内/外都能用**:组件内 `model()` / `model(selector)` 订阅更新;组件外用 `model.data / model.getState()` 获取最新快照(不触发渲染)。
13
- * **内置优化**:对象更新自动合并、无变化不更新(减少无意义重渲染)。
14
- * **生产力优先**:可选持久化(Persist)、可选 Web 多窗口同步(BroadcastChannel)。
13
+ ```bash
14
+ npm i react-native-hox
15
+ ```
15
16
 
16
- ---
17
+ ### 最小示例
17
18
 
18
- ## 快速上手(推荐)
19
+ ```ts
20
+ // stores/user.ts
21
+ import { createModel } from 'react-native-hox';
19
22
 
20
- ### 安装
23
+ export const userStore = createModel({ name: 'Guest' }); // 数据只存在内存
24
+ // export const userStore = createModel({ name: 'Guest' }, { persist: 'user_v1' }); // 数据存在缓存 AsyncStorage
25
+ ```
21
26
 
22
- ```bash
23
- npm install react-native-hox
24
- # or
25
- yarn add react-native-hox
27
+ ```tsx
28
+ // Profile.tsx
29
+ import React from 'react';
30
+ import { View, Text, Button } from 'react-native';
31
+ import { userStore } from './stores/user';
32
+
33
+ export function Profile() {
34
+ const user = userStore.getState();
35
+ return (
36
+ <View>
37
+ <Text>Hello, {user.name}</Text>
38
+ <Button title="Set Name" onPress={() => userStore.setState({ name: 'Tom' })} />
39
+ </View>
40
+ );
41
+ }
26
42
  ```
27
43
 
28
- ### 创建一个全局模型
44
+ ## 功能点
29
45
 
30
- 适用于用户信息、全局配置、表单草稿、token 等场景。
31
- **完全不需要 Provider,也不需要修改入口文件。**
46
+ ### 1) 更新状态:自动合并 / 显式替换
32
47
 
33
- ```tsx
34
- // store.ts
48
+ ```ts
35
49
  import { createModel } from 'react-native-hox';
36
50
 
37
- // 定义一个支持持久化的用户状态
38
- // createModel(initialState, options?)
39
- export const userModel = createModel({
40
- name: 'Guest',
41
- token: '',
42
- theme: 'light'
43
- }, {
44
- persist: 'user_store', // 开启持久化,自动写入 Storage
45
- });
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';
46
58
 
47
- // 推荐:在组件内使用 useXxxModel 作为 Hook 别名(同一个对象)
48
- // 这样更符合 Hook 心智模型,也能被 eslint-plugin-react-hooks 正确识别
49
- export const useUserModel = userModel;
59
+ export function UpdateExample() {
60
+ const profile = profileStore.getState();
61
+
62
+ return (
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
+ }
50
71
  ```
51
72
 
52
- **组件中使用:**
73
+ ### 2) Selector:只在关注的数据变化时重渲染
53
74
 
54
- ```tsx
55
- import { useUserModel } from './store';
75
+ ```ts
76
+ import { createModel } from 'react-native-hox';
77
+
78
+ export const counterStore = createModel({ count: 0, text: 'a' });
79
+ ```
56
80
 
57
- function UserProfile() {
58
- // 直接调用 model() 获取响应式 state
59
- const user = useUserModel.useState();
81
+ ```tsx
82
+ import React from 'react';
83
+ import { View, Text, Button } from 'react-native';
84
+ import { counterStore } from './stores/counter';
60
85
 
61
- const login = () => {
62
- // 自动合并更新:只更新 token name,theme 保持不变
63
- useUserModel.setState({
64
- token: 'xyz',
65
- name: 'User'
66
- });
67
- };
86
+ export function SelectorExample() {
87
+ const count = counterStore.getState((s) => s.count);
68
88
 
69
89
  return (
70
90
  <View>
71
- <Text>{user.name}</Text>
72
- <Button onPress={login}>Login</Button>
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()) })} />
73
94
  </View>
74
95
  );
75
96
  }
76
97
  ```
77
98
 
78
- > 说明:组件渲染是否更新,取决于你是否用 `useUserModel.useState()` / `useUserModel.useState(selector)`(或直接 `useUserModel()`)订阅了状态;`userModel.data` / `useUserModel.data` 是快照不会触发渲染。`setState` 如果没有真正改变值会被自动忽略(减少无意义重渲染)。
99
+ selector 返回对象/数组时,传入 `equalityFn`(例如 `shallow`)避免无意义重渲染:
79
100
 
80
- **什么时候用 `useXxxModel()` / `getState()` / `data`?**
101
+ ```tsx
102
+ import { shallow } from 'react-native-hox';
103
+ import { counterStore } from './stores/counter';
81
104
 
82
- - 组件渲染里:用 `useUserModel.useState()`(或 `useUserModel.useState(s => s.token)`),它会订阅更新,状态变了组件会跟着重渲染
83
- - 组件外(网络请求拦截器、工具函数、非 React 环境):用 `userModel.getState()` / `userModel.data` 读取最新快照,或 `userModel.subscribe(...)` 订阅
84
- - 不建议在组件渲染里用 `useUserModel.getState()` / `useUserModel.data`:它们是“快照读取”,不会触发组件更新,容易出现 UI 不刷新
105
+ const picked = counterStore.getState((s) => ({ count: s.count }), shallow);
106
+ ```
85
107
 
86
- **组件外使用:**
108
+ ### 3) 组件外读写:Vanilla API
87
109
 
88
110
  ```ts
89
- import { userModel } from './store';
111
+ import { userStore } from './stores/user';
112
+
113
+ const name1 = userStore.data.state.name;
114
+ const name2 = userStore.data.getState().name;
90
115
 
91
- // Axios 拦截器中读取 Token
92
- axios.interceptors.request.use(config => {
93
- const { token } = userModel.data; // 直接获取最新快照(非 Hook)
94
- if (token) config.headers.Authorization = token;
95
- return config;
116
+ const unsub = userStore.data.subscribe((next, prev) => {
117
+ console.log(next, prev);
96
118
  });
119
+ unsub();
120
+ ```
97
121
 
98
- // 在任意函数中更新状态
99
- export function logout() {
100
- userModel.setState({ token: '', name: 'Guest' });
101
- }
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' });
102
130
  ```
103
131
 
104
- ---
132
+ 如果你希望替换存储引擎,可以在 `persist.storage` 传入实现同等接口的存储对象:
133
+
134
+ ```ts
135
+ import { createModel } from 'react-native-hox';
136
+ import type { StorageEngine } from 'react-native-hox';
105
137
 
106
- ## 全局配置(可选)
138
+ const storage: StorageEngine = {
139
+ getItem: async (key) => null,
140
+ setItem: async (key, value) => {},
141
+ removeItem: async (key) => {},
142
+ };
107
143
 
108
- `persist` 默认会使用 **Web 端的 `localStorage`(零配置)**。在 React Native 环境没有默认持久化存储,需要你通过 `setup` 注入(也可以用 `setup` 覆盖 Web 默认实现)。
144
+ export const authStore = createModel({ token: '' }, { persist: { key: 'auth_v1', storage } });
145
+ ```
146
+
147
+ ### 5) reset / destroy / strict
109
148
 
110
149
  ```ts
111
- import { setup } from 'react-native-hox';
112
- import AsyncStorage from '@react-native-async-storage/async-storage';
113
- import { MMKV } from 'react-native-mmkv';
114
-
115
- // 适配 AsyncStorage
116
- setup({ storage: AsyncStorage });
117
-
118
- // 或者适配 MMKV
119
- const storage = new MMKV();
120
- setup({
121
- storage: {
122
- getItem: (key) => storage.getString(key),
123
- setItem: (key, val) => storage.set(key, val),
124
- removeItem: (key) => storage.delete(key),
125
- }
126
- });
150
+ import { userStore } from './stores/user';
151
+
152
+ userStore.reset();
153
+ userStore.destroy();
127
154
  ```
128
155
 
129
- ## 高级特性
156
+ ```ts
157
+ import { createModel } from 'react-native-hox';
158
+
159
+ export const strictStore = createModel(
160
+ { count: 0 },
161
+ { strict: { forbidSetStateAfterDestroy: true } }
162
+ );
163
+ ```
130
164
 
131
- ### Web 端多窗口同步
165
+ ## 进阶使用
132
166
 
133
- 如果你在使用 `react-native-web`,或者在 WebView 场景下需要多 Tab 状态同步,可以开启 `sync` 选项。
167
+ ### 1) subscribe(selector):只在 slice 变化时触发
168
+
169
+ ```tsx
170
+ import React, { useEffect } from 'react';
171
+ import { View, Text } from 'react-native';
172
+ import { userStore } from './stores/user';
173
+
174
+ export function SubscribeExample() {
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
+ }
194
+ ```
195
+
196
+ ### 2) 持久化安全边界:版本化与数据迁移
134
197
 
135
198
  ```ts
136
- const userModel = createModel({ ... }, {
137
- persist: 'user',
138
- });
199
+ import { createModel } from 'react-native-hox';
200
+
201
+ export const userPersistStore = createModel(
202
+ { token: '', isLogin: false },
203
+ {
204
+ persist: {
205
+ key: 'user_v1',
206
+ beforeRecover: (v) => ({
207
+ token: v?.token ?? '',
208
+ isLogin: Boolean(v?.token),
209
+ }),
210
+ },
211
+ }
212
+ );
213
+ ```
214
+
215
+ ### 3) initialState 支持纯函数
216
+
217
+ ```ts
218
+ import { createModel } from 'react-native-hox';
219
+
220
+ export const appStore = createModel(() => ({ bootAt: Date.now() }));
139
221
  ```
222
+
223
+ ## API 参考(简版)
224
+
225
+ ### `createModel(initialState, options?)`
226
+
227
+ - `initialState`: `T` 或 `() => T`
228
+ - `options.persist`: `string` 或 `{ key, storage?, debounce?, beforePersist?, beforeRecover? }`
229
+ - `options.strict.forbidSetStateAfterDestroy`: `boolean`
230
+ - `options.logger`: `{ warn, error }`
231
+
232
+ 返回的 `Model` 同时支持:
233
+
234
+ - 组件内(Hook):`model.getState()` / `model.use()` / `model.useState()`
235
+ - 组件外(Vanilla):`model.data.state` / `model.data.getState()` / `model.data.setState()` / `model.data.subscribe()`
236
+
237
+ ## License
238
+
239
+ ISC
package/dist/index.d.mts CHANGED
@@ -1,4 +1,9 @@
1
1
  type Listener<T> = (state: T, prevState: T) => void;
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
+ }
2
7
  type EqualityFn<T> = (a: T, b: T) => boolean;
3
8
 
4
9
  type Selector<T, U> = (state: T) => U;
@@ -8,58 +13,49 @@ type ModelHook<T> = {
8
13
  };
9
14
  interface PersistOptions<T> {
10
15
  key: string;
16
+ storage?: StorageEngine;
11
17
  debounce?: number;
12
18
  beforePersist?: (state: T) => any;
13
- beforeRecover?: (value: any) => T;
19
+ beforeRecover?: (value: any) => T | Partial<T>;
20
+ }
21
+ interface Logger {
22
+ warn: (...args: any[]) => void;
23
+ error: (...args: any[]) => void;
14
24
  }
15
25
  interface CreateModelOptions<T> {
16
- /**
17
- * 开启持久化。建议直接传 key:persist: 'user_store'
18
- */
19
26
  persist?: string | PersistOptions<T>;
20
- /**
21
- * Web 多窗口同步(BroadcastChannel)。需要 persist key 才能工作。
22
- */
23
- sync?: boolean;
27
+ logger?: Logger;
28
+ strict?: {
29
+ forbidSetStateAfterDestroy?: boolean;
30
+ };
24
31
  }
25
32
  interface SubscribeWithSelectorOptions<U> {
26
33
  equalityFn?: EqualityFn<U>;
27
34
  fireImmediately?: boolean;
28
35
  }
29
- interface Model<T> {
30
- (): T;
31
- <U>(selector: Selector<T, U>, equalityFn?: EqualityFn<U>): U;
32
- /**
33
- * 更显式的 Hook 入口(等价于直接调用 model())
34
- * 推荐在组件内写:const state = useUserModel.useState()
35
- */
36
- useState: ModelHook<T>;
37
- /**
38
- * useState 的别名(更短),保留给喜欢 `useXxx.use()` 风格的团队
39
- */
40
- use: ModelHook<T>;
36
+ interface Subscribe<T> {
37
+ (listener: Listener<T>): () => void;
38
+ <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
39
+ }
40
+ interface ModelData<T> {
41
41
  getState: () => T;
42
42
  setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
43
- subscribe: {
44
- (listener: Listener<T>): () => void;
45
- <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
46
- };
43
+ subscribe: Subscribe<T>;
47
44
  destroy: () => void;
48
45
  reset: () => void;
49
- data: T;
46
+ state: T;
50
47
  }
48
+ type Model<T> = ModelHook<T> & {
49
+ getState: ModelHook<T>;
50
+ 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
+ use: ModelHook<T>;
56
+ };
51
57
  declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
52
58
 
53
- interface StorageEngine {
54
- getItem: (key: string) => string | null | Promise<string | null>;
55
- setItem: (key: string, value: string) => void | Promise<void>;
56
- removeItem: (key: string) => void | Promise<void>;
57
- }
58
- interface Config {
59
- storage?: StorageEngine;
60
- }
61
- declare const setup: (options: Config) => void;
62
-
63
- declare const shallow: EqualityFn<any>;
59
+ declare function shallow<T>(objA: T, objB: T): boolean;
64
60
 
65
- export { type CreateModelOptions, type Model, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, setup, shallow };
61
+ export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  type Listener<T> = (state: T, prevState: T) => void;
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
+ }
2
7
  type EqualityFn<T> = (a: T, b: T) => boolean;
3
8
 
4
9
  type Selector<T, U> = (state: T) => U;
@@ -8,58 +13,49 @@ type ModelHook<T> = {
8
13
  };
9
14
  interface PersistOptions<T> {
10
15
  key: string;
16
+ storage?: StorageEngine;
11
17
  debounce?: number;
12
18
  beforePersist?: (state: T) => any;
13
- beforeRecover?: (value: any) => T;
19
+ beforeRecover?: (value: any) => T | Partial<T>;
20
+ }
21
+ interface Logger {
22
+ warn: (...args: any[]) => void;
23
+ error: (...args: any[]) => void;
14
24
  }
15
25
  interface CreateModelOptions<T> {
16
- /**
17
- * 开启持久化。建议直接传 key:persist: 'user_store'
18
- */
19
26
  persist?: string | PersistOptions<T>;
20
- /**
21
- * Web 多窗口同步(BroadcastChannel)。需要 persist key 才能工作。
22
- */
23
- sync?: boolean;
27
+ logger?: Logger;
28
+ strict?: {
29
+ forbidSetStateAfterDestroy?: boolean;
30
+ };
24
31
  }
25
32
  interface SubscribeWithSelectorOptions<U> {
26
33
  equalityFn?: EqualityFn<U>;
27
34
  fireImmediately?: boolean;
28
35
  }
29
- interface Model<T> {
30
- (): T;
31
- <U>(selector: Selector<T, U>, equalityFn?: EqualityFn<U>): U;
32
- /**
33
- * 更显式的 Hook 入口(等价于直接调用 model())
34
- * 推荐在组件内写:const state = useUserModel.useState()
35
- */
36
- useState: ModelHook<T>;
37
- /**
38
- * useState 的别名(更短),保留给喜欢 `useXxx.use()` 风格的团队
39
- */
40
- use: ModelHook<T>;
36
+ interface Subscribe<T> {
37
+ (listener: Listener<T>): () => void;
38
+ <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
39
+ }
40
+ interface ModelData<T> {
41
41
  getState: () => T;
42
42
  setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
43
- subscribe: {
44
- (listener: Listener<T>): () => void;
45
- <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
46
- };
43
+ subscribe: Subscribe<T>;
47
44
  destroy: () => void;
48
45
  reset: () => void;
49
- data: T;
46
+ state: T;
50
47
  }
48
+ type Model<T> = ModelHook<T> & {
49
+ getState: ModelHook<T>;
50
+ 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
+ use: ModelHook<T>;
56
+ };
51
57
  declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
52
58
 
53
- interface StorageEngine {
54
- getItem: (key: string) => string | null | Promise<string | null>;
55
- setItem: (key: string, value: string) => void | Promise<void>;
56
- removeItem: (key: string) => void | Promise<void>;
57
- }
58
- interface Config {
59
- storage?: StorageEngine;
60
- }
61
- declare const setup: (options: Config) => void;
62
-
63
- declare const shallow: EqualityFn<any>;
59
+ declare function shallow<T>(objA: T, objB: T): boolean;
64
60
 
65
- export { type CreateModelOptions, type Model, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, setup, shallow };
61
+ export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };