react-native-hox 0.0.1-beta → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,29 @@
1
1
  # react-native-hox
2
2
 
3
- **为复杂业务场景而生的状态管理解决方案**。
3
+ > **下一代 React Native 状态管理方案。**
4
+ > 极简、高性能、生产级特性开箱即用。
4
5
 
5
- 我们把 API 收敛到一个:`createModel`。组件内像 Hook 一样用,组件外(如网络请求拦截器)也能直接读取/订阅最新状态。
6
+ [![NPM Version](https://img.shields.io/npm/v/react-native-hox)](https://www.npmjs.com/package/react-native-hox)
7
+ [![TypeScript](https://img.shields.io/badge/language-TypeScript-blue)](https://www.typescriptlang.org/)
8
+ [![License](https://img.shields.io/npm/l/react-native-hox)](https://github.com/wangwenshan/react-native-hox/blob/main/LICENSE)
6
9
 
7
- ---
10
+ ## 为什么选择 Hox?
11
+
12
+ 在 React Native 开发中,我们经常在 Redux 的繁琐、Context 的性能陷阱和 Zustand 的手动配置之间挣扎。
13
+
14
+ `react-native-hox` 重新思考了移动端状态管理的最佳实践,提供了一种**"零配置、全功能"**的开发体验。它不仅是一个状态库,更内置了移动端开发必不可少的工程化能力。
8
15
 
9
- ## 核心特性
16
+ ### 核心亮点
10
17
 
11
- * **一个 API**:`createModel` 创建全局模型。
12
- * **组件内/外都能用**:组件内 `model()` / `model(selector)` 订阅更新;组件外用 `model.data / model.getState()` 获取最新快照(不触发渲染)。
13
- * **内置优化**:对象更新自动合并、无变化不更新(减少无意义重渲染)。
14
- * **生产力优先**:可选持久化(Persist)、可选 Web 多窗口同步(BroadcastChannel)。
18
+ * ⚡️ **零样板代码**:告别 Action、Reducer、Provider。一行代码即可创建全局状态。
19
+ * 💾 **智能持久化**:内置异步存储支持。只需配置 `persist` key,自动处理防抖写入与初始化读取。
20
+ * 🧬 **自动合并 (Auto-Merge)**:延续 `setState` 的直觉体验,自动合并对象属性,无需手动 spread `...state`。
21
+ * 🎯 **精准渲染**:支持 Selector 选择器,确保组件仅在关注的数据变化时更新。
22
+ * 🔌 **脱离组件运行**:完整的 Vanilla API,支持在 Axios 拦截器、事件回调等非组件环境读写状态。
15
23
 
16
24
  ---
17
25
 
18
- ## 快速上手(推荐)
26
+ ## 快速开始
19
27
 
20
28
  ### 安装
21
29
 
@@ -25,115 +33,180 @@ npm install react-native-hox
25
33
  yarn add react-native-hox
26
34
  ```
27
35
 
28
- ### 创建一个全局模型
36
+ ### 1. 基础用法:全局状态 (Global Store)
29
37
 
30
- 适用于用户信息、全局配置、表单草稿、token 等场景。
31
- **完全不需要 Provider,也不需要修改入口文件。**
38
+ 适用于:用户信息、应用配置、Token 管理等。
32
39
 
33
- ```tsx
34
- // store.ts
40
+ ```typescript
41
+ // store/user.ts
35
42
  import { createModel } from 'react-native-hox';
36
43
 
37
- // 定义一个支持持久化的用户状态
38
- // createModel(initialState, options?)
39
- export const userModel = createModel({
44
+ // 创建一个自动持久化的用户 Store
45
+ export const userStore = createModel({
40
46
  name: 'Guest',
41
47
  token: '',
42
- theme: 'light'
48
+ isLogin: false,
43
49
  }, {
44
- persist: 'user_store', // 开启持久化,自动写入 Storage
50
+ persist: 'user_v1', // 开启持久化,数据自动存入 AsyncStorage/localStorage
45
51
  });
46
-
47
- // 推荐:在组件内使用 useXxxModel 作为 Hook 别名(同一个对象)
48
- // 这样更符合 Hook 心智模型,也能被 eslint-plugin-react-hooks 正确识别
49
- export const useUserModel = userModel;
50
52
  ```
51
53
 
52
- **组件中使用:**
53
-
54
54
  ```tsx
55
- import { useUserModel } from './store';
55
+ // components/UserProfile.tsx
56
+ import { userStore } from '../store/user';
56
57
 
57
- function UserProfile() {
58
- // 直接调用 model() 获取响应式 state
59
- const user = useUserModel.useState();
58
+ export default function UserProfile() {
59
+ // 1. 读取状态 (Hook)
60
+ // 支持传入 selector,仅在关注的属性变化时重渲染
61
+ const name = userStore.getState(s => s.name);
60
62
 
61
63
  const login = () => {
62
- // 自动合并更新:只更新 token 和 name,theme 保持不变
63
- useUserModel.setState({
64
- token: 'xyz',
65
- name: 'User'
64
+ // 2. 更新状态:自动合并,无需 ...user
65
+ userStore.setState({
66
+ name: 'Admin',
67
+ isLogin: true
66
68
  });
67
69
  };
68
70
 
69
71
  return (
70
72
  <View>
71
- <Text>{user.name}</Text>
72
- <Button onPress={login}>Login</Button>
73
+ <Text>Hello, {name}</Text>
74
+ <Button onPress={login} title="Login" />
73
75
  </View>
74
76
  );
75
77
  }
76
78
  ```
77
79
 
78
- > 说明:组件渲染是否更新,取决于你是否用 `useUserModel.useState()` / `useUserModel.useState(selector)`(或直接 `useUserModel()`)订阅了状态;`userModel.data` / `useUserModel.data` 是快照不会触发渲染。`setState` 如果没有真正改变值会被自动忽略(减少无意义重渲染)。
80
+ ### 2. 进阶用法:精细控制 (Selector & Reset)
79
81
 
80
- **什么时候用 `useXxxModel()` / `getState()` / `data`?**
82
+ ```tsx
83
+ // 只有当 count > 10 发生变化时,组件才会重渲染
84
+ const isHigh = userStore.getState(state => state.count > 10);
85
+
86
+ // 重置状态为初始值
87
+ userStore.reset();
88
+ ```
81
89
 
82
- - 组件渲染里:用 `useUserModel.useState()`(或 `useUserModel.useState(s => s.token)`),它会订阅更新,状态变了组件会跟着重渲染
83
- - 组件外(网络请求拦截器、工具函数、非 React 环境):用 `userModel.getState()` / `userModel.data` 读取最新快照,或 `userModel.subscribe(...)` 订阅
84
- - 不建议在组件渲染里用 `useUserModel.getState()` / `useUserModel.data`:它们是“快照读取”,不会触发组件更新,容易出现 UI 不刷新
90
+ ---
85
91
 
86
- **组件外使用:**
92
+ ## 解决痛点
87
93
 
88
- ```ts
89
- import { userModel } from './store';
94
+ ### 痛点 1:持久化配置太麻烦
95
+ **以前**:安装中间件 -> 配置白名单 -> 处理 Hydration -> 处理防抖。
96
+ **现在**:
97
+
98
+ ```typescript
99
+ const store = createModel({ theme: 'dark' }, {
100
+ persist: 'app_theme' // ✅ Done. 自动防抖,自动恢复。
101
+ });
102
+ ```
103
+
104
+ ### 痛点 2:非组件环境无法访问状态
105
+ **以前**:为了在 API 拦截器里拿 Token,不得不把 store 挂载到 window 或者引入复杂的 hack。
106
+ **现在**:
107
+
108
+ ```typescript
109
+ // api.ts
110
+ import { userStore } from './store/user';
90
111
 
91
- // 在 Axios 拦截器中读取 Token
92
112
  axios.interceptors.request.use(config => {
93
- const { token } = userModel.data; // 直接获取最新快照(非 Hook)
94
- if (token) config.headers.Authorization = token;
113
+ // 直接读取最新状态
114
+ const token = userStore.data.state.token;
115
+ if (token) {
116
+ config.headers.Authorization = `Bearer ${token}`;
117
+ }
95
118
  return config;
96
119
  });
97
120
 
98
- // 在任意函数中更新状态
99
- export function logout() {
100
- userModel.setState({ token: '', name: 'Guest' });
101
- }
102
- ```
121
+ // 甚至可以直接修改
122
+ userStore.data.setState({ token: '' }); // 登出
123
+ ```+ ### 3. API 统一性 (New)
124
+ + **以前**:Redux `useSelector` vs `dispatch`,Context `useContext` vs `dispatch`。
125
+ + **现在**:
126
+ + * 读 (React):`userStore.getState()`
127
+ + * 读 (Vanilla):`userStore.data.state`
128
+ + * 写 (Universal):`userStore.setState()`
103
129
 
104
- ---
130
+ ## 生产实践
105
131
 
106
- ## 全局配置(可选)
107
-
108
- `persist` 默认会使用 **Web 端的 `localStorage`(零配置)**。在 React Native 环境没有默认持久化存储,需要你通过 `setup` 注入(也可以用 `setup` 覆盖 Web 默认实现)。
132
+ ### 1) Token 管理:拦截器读取 + 401 统一登出
109
133
 
110
134
  ```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),
135
+ import axios from 'axios';
136
+ import { userStore } from './store/user';
137
+
138
+ axios.interceptors.request.use((config) => {
139
+ const token = userStore.data.state.token;
140
+ if (token) {
141
+ config.headers = config.headers ?? {};
142
+ config.headers.Authorization = `Bearer ${token}`;
125
143
  }
144
+ return config;
126
145
  });
127
- ```
128
146
 
129
- ## 高级特性
147
+ axios.interceptors.response.use(
148
+ (resp) => resp,
149
+ (err) => {
150
+ if (err?.response?.status === 401) {
151
+ userStore.setState({ token: '', isLogin: false });
152
+ }
153
+ return Promise.reject(err);
154
+ }
155
+ );
156
+ ```
130
157
 
131
- ### Web 端多窗口同步
158
+ ### 2) 持久化:推荐的安全边界
132
159
 
133
- 如果你在使用 `react-native-web`,或者在 WebView 场景下需要多 Tab 状态同步,可以开启 `sync` 选项。
160
+ * `persist` 的恢复是异步的:首屏会先用 `initialState`,恢复完成后再更新一次。
161
+ * 强烈建议把版本号编码进 key(如 `user_v1`),避免不可控的 schema 变化。
162
+ * 需要兼容旧数据时,用 `beforeRecover` 做兜底转换。
134
163
 
135
164
  ```ts
136
- const userModel = createModel({ ... }, {
137
- persist: 'user',
138
- });
165
+ export const userStore = createModel(
166
+ { token: '', isLogin: false },
167
+ {
168
+ persist: {
169
+ key: 'user_v1',
170
+ beforeRecover: (v) => ({
171
+ token: v?.token ?? '',
172
+ isLogin: Boolean(v?.token),
173
+ }),
174
+ },
175
+ }
176
+ );
177
+ ```
178
+
179
+ ### 3) Selector 性能:避免无意义重渲染
180
+
181
+ * selector 尽量返回 primitive(string/number/boolean)或稳定引用。
182
+ * 如果 selector 返回对象/数组,传入 `equalityFn`(例如 `shallow`)以避免每次新建对象触发重渲染。
183
+
184
+ ```tsx
185
+ import { shallow } from 'react-native-hox';
186
+
187
+ const user = userStore.getState((s) => ({ name: s.name, isLogin: s.isLogin }), shallow);
139
188
  ```
189
+
190
+ ## API 参考
191
+
192
+ ### `createModel(initialState, options?)`
193
+
194
+ 创建全局 Store。
195
+
196
+ * `initialState`: 初始状态对象,或返回初始状态的纯函数。
197
+ * `options`:
198
+ * `persist`: `string` (可选) - 持久化存储的唯一 Key。
199
+
200
+ 返回 `Model` 实例:
201
+ * `getState(selector?, equalityFn?)`: React Hook,在组件中读取状态。
202
+ * `useState(selector?, equalityFn?)`: `getState` 的别名。
203
+ * `use(selector?, equalityFn?)`: `getState` 的别名。
204
+ * `setState(patch)`: 更新状态。
205
+ * `reset()`: 重置为初始状态。
206
+ * `data`: 组件外 API,包含 `state` (getter), `getState` (fn), `setState`, `subscribe`, `reset`, `destroy`。
207
+
208
+ ---
209
+
210
+ ## License
211
+
212
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,3 +1,9 @@
1
+ interface StorageEngine {
2
+ getItem: (key: string) => string | null | Promise<string | null>;
3
+ setItem: (key: string, value: string) => void | Promise<void>;
4
+ removeItem: (key: string) => void | Promise<void>;
5
+ }
6
+
1
7
  type Listener<T> = (state: T, prevState: T) => void;
2
8
  type EqualityFn<T> = (a: T, b: T) => boolean;
3
9
 
@@ -8,58 +14,49 @@ type ModelHook<T> = {
8
14
  };
9
15
  interface PersistOptions<T> {
10
16
  key: string;
17
+ storage?: StorageEngine;
11
18
  debounce?: number;
12
19
  beforePersist?: (state: T) => any;
13
- beforeRecover?: (value: any) => T;
20
+ beforeRecover?: (value: any) => T | Partial<T>;
21
+ }
22
+ interface Logger {
23
+ warn: (...args: any[]) => void;
24
+ error: (...args: any[]) => void;
14
25
  }
15
26
  interface CreateModelOptions<T> {
16
- /**
17
- * 开启持久化。建议直接传 key:persist: 'user_store'
18
- */
19
27
  persist?: string | PersistOptions<T>;
20
- /**
21
- * Web 多窗口同步(BroadcastChannel)。需要 persist key 才能工作。
22
- */
23
- sync?: boolean;
28
+ logger?: Logger;
29
+ strict?: {
30
+ forbidSetStateAfterDestroy?: boolean;
31
+ };
24
32
  }
25
33
  interface SubscribeWithSelectorOptions<U> {
26
34
  equalityFn?: EqualityFn<U>;
27
35
  fireImmediately?: boolean;
28
36
  }
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>;
37
+ interface Subscribe<T> {
38
+ (listener: Listener<T>): () => void;
39
+ <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
40
+ }
41
+ interface ModelData<T> {
41
42
  getState: () => T;
42
43
  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
- };
44
+ subscribe: Subscribe<T>;
47
45
  destroy: () => void;
48
46
  reset: () => void;
49
- data: T;
47
+ state: T;
50
48
  }
49
+ type Model<T> = ModelHook<T> & {
50
+ getState: ModelHook<T>;
51
+ useState: ModelHook<T>;
52
+ setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
53
+ destroy: () => void;
54
+ reset: () => void;
55
+ data: ModelData<T>;
56
+ use: ModelHook<T>;
57
+ };
51
58
  declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
52
59
 
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>;
60
+ declare function shallow<T>(objA: T, objB: T): boolean;
64
61
 
65
- export { type CreateModelOptions, type Model, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, setup, shallow };
62
+ export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ interface StorageEngine {
2
+ getItem: (key: string) => string | null | Promise<string | null>;
3
+ setItem: (key: string, value: string) => void | Promise<void>;
4
+ removeItem: (key: string) => void | Promise<void>;
5
+ }
6
+
1
7
  type Listener<T> = (state: T, prevState: T) => void;
2
8
  type EqualityFn<T> = (a: T, b: T) => boolean;
3
9
 
@@ -8,58 +14,49 @@ type ModelHook<T> = {
8
14
  };
9
15
  interface PersistOptions<T> {
10
16
  key: string;
17
+ storage?: StorageEngine;
11
18
  debounce?: number;
12
19
  beforePersist?: (state: T) => any;
13
- beforeRecover?: (value: any) => T;
20
+ beforeRecover?: (value: any) => T | Partial<T>;
21
+ }
22
+ interface Logger {
23
+ warn: (...args: any[]) => void;
24
+ error: (...args: any[]) => void;
14
25
  }
15
26
  interface CreateModelOptions<T> {
16
- /**
17
- * 开启持久化。建议直接传 key:persist: 'user_store'
18
- */
19
27
  persist?: string | PersistOptions<T>;
20
- /**
21
- * Web 多窗口同步(BroadcastChannel)。需要 persist key 才能工作。
22
- */
23
- sync?: boolean;
28
+ logger?: Logger;
29
+ strict?: {
30
+ forbidSetStateAfterDestroy?: boolean;
31
+ };
24
32
  }
25
33
  interface SubscribeWithSelectorOptions<U> {
26
34
  equalityFn?: EqualityFn<U>;
27
35
  fireImmediately?: boolean;
28
36
  }
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>;
37
+ interface Subscribe<T> {
38
+ (listener: Listener<T>): () => void;
39
+ <U>(selector: Selector<T, U>, listener: (selected: U, prevSelected: U) => void, options?: SubscribeWithSelectorOptions<U>): () => void;
40
+ }
41
+ interface ModelData<T> {
41
42
  getState: () => T;
42
43
  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
- };
44
+ subscribe: Subscribe<T>;
47
45
  destroy: () => void;
48
46
  reset: () => void;
49
- data: T;
47
+ state: T;
50
48
  }
49
+ type Model<T> = ModelHook<T> & {
50
+ getState: ModelHook<T>;
51
+ useState: ModelHook<T>;
52
+ setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean) => void;
53
+ destroy: () => void;
54
+ reset: () => void;
55
+ data: ModelData<T>;
56
+ use: ModelHook<T>;
57
+ };
51
58
  declare function createModel<T>(initialState: T | (() => T), options?: CreateModelOptions<T>): Model<T>;
52
59
 
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>;
60
+ declare function shallow<T>(objA: T, objB: T): boolean;
64
61
 
65
- export { type CreateModelOptions, type Model, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, setup, shallow };
62
+ export { type CreateModelOptions, type Model, type ModelData, type PersistOptions, type StorageEngine, type SubscribeWithSelectorOptions, createModel, shallow };