@e7w/easy-model 0.1.7 → 0.1.9

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,530 +1,298 @@
1
- # easy-model
2
-
3
- 一个功能强大的 React 状态管理库,集成了状态管理、IoC 容器、观察者模式和加载器等功能。
4
-
5
- ## 特性
6
-
7
- - 🚀 **简单易用** - 基于类的状态管理,直观的 API 设计
8
- - 🔄 **响应式** - 自动观察状态变化,精确更新组件
9
- - 💉 **IoC 容器** - 内置依赖注入容器,支持命名空间隔离
10
- - 🎯 **类型安全** - 完整的 TypeScript 支持,基于 Zod 的运行时类型验证
11
- - **性能优化** - 智能缓存和垃圾回收机制
12
- - 🔧 **加载状态管理** - 内置异步操作加载状态管理
13
- - 🧩 **模块化** - 可按需使用各个功能模块
14
-
15
- ## 安装
16
-
17
- ```bash
18
- npm install @e7w/easy-model
19
- #
20
- yarn add @e7w/easy-model
21
- #
22
- pnpm add @e7w/easy-model
23
- ```
24
-
25
- ## 核心概念
26
-
27
- ### 1. 状态管理
28
-
29
- 基于类的状态管理,支持自动观察和响应式更新。
30
-
31
- ```tsx
32
- import { useModel } from "@e7w/easy-model";
33
-
34
- class CounterModel {
35
- constructor(public name: string) {}
36
-
37
- count = 0;
38
-
39
- increment() {
40
- this.count++;
41
- }
42
-
43
- decrement() {
44
- this.count--;
45
- }
46
-
47
- reset() {
48
- this.count = 0;
49
- }
50
- }
51
-
52
- function Counter() {
53
- const { count, increment, decrement, reset } = useModel(CounterModel, [
54
- "main",
55
- ]);
56
-
57
- return (
58
- <div>
59
- <h2>计数器: {count}</h2>
60
- <button onClick={increment}>+1</button>
61
- <button onClick={decrement}>-1</button>
62
- <button onClick={reset}>重置</button>
63
- </div>
64
- );
65
- }
66
- ```
67
-
68
- ### 2. IoC 容器
69
-
70
- 内置的依赖注入容器,支持值注入、构造函数注入和装饰器注入。
71
-
72
- ```tsx
73
- import { Container, VInjection, CInjection, inject } from "@e7w/easy-model";
74
- import { z } from "zod";
75
-
76
- // 定义配置 schema
77
- const configSchema = z.object({
78
- apiUrl: z.string(),
79
- timeout: z.number(),
80
- });
81
-
82
- const loggerSchema = z.object({
83
- log: z.function(),
84
- });
85
-
86
- // 定义服务类
87
- class Logger {
88
- log(message: string) {
89
- console.log(`[LOG] ${message}`);
90
- }
91
- }
92
-
93
- class ApiService {
94
- @inject(configSchema)
95
- config: z.infer<typeof configSchema> = { apiUrl: "", timeout: 0 };
96
-
97
- @inject(loggerSchema)
98
- logger: Logger | undefined;
99
-
100
- async fetchData() {
101
- this.logger?.log(`Fetching data from ${this.config.apiUrl}`);
102
- // 实际的 API 调用逻辑
103
- }
104
- }
105
-
106
- function App() {
107
- return (
108
- <Container namespace="app">
109
- {/* 注入配置值 */}
110
- <VInjection
111
- schema={configSchema}
112
- val={{ apiUrl: "https://api.example.com", timeout: 5000 }}
113
- />
114
-
115
- {/* 注入构造函数 */}
116
- <CInjection schema={loggerSchema} ctor={Logger} />
117
-
118
- <YourComponents />
119
- </Container>
120
- );
121
- }
122
- ```
123
-
124
- ### 3. 观察者模式
125
-
126
- 手动观察对象变化,适用于非 React 环境。注意:`observe` 函数是内部实现,不对外导出。
127
-
128
- ```tsx
129
- import { watch } from "@e7w/easy-model";
130
-
131
- // 注意:observe 函数未导出,这里仅作为概念说明
132
- // 实际使用中,观察功能已集成在 useModel 和 useInstance 中
133
-
134
- // 监听对象变化的概念示例(实际需要通过 useModel 等方式)
135
- const unwatch = watch(someObservableObject, (path, oldValue, newValue) => {
136
- console.log(`属性 ${path.join(".")} 从 ${oldValue} 变为 ${newValue}`);
137
- });
138
-
139
- // 取消监听
140
- unwatch();
141
- ```
142
-
143
- ### 4. 加载状态管理
144
-
145
- 内置的异步操作加载状态管理,支持全局和局部加载状态。
146
-
147
- ```tsx
148
- import { loader } from "@e7w/easy-model";
149
-
150
- class DataService {
151
- data: any[] = [];
152
-
153
- @loader.load(true) // true 表示全局加载状态
154
- async fetchData() {
155
- const response = await fetch("/api/data");
156
- this.data = await response.json();
157
- return this.data;
158
- }
159
-
160
- @loader.load() // 局部加载状态
161
- async updateItem(id: string, updates: any) {
162
- const response = await fetch(`/api/data/${id}`, {
163
- method: "PUT",
164
- body: JSON.stringify(updates),
165
- });
166
- return response.json();
167
- }
168
-
169
- @loader.once() // 只执行一次的异步操作
170
- async initializeApp() {
171
- // 初始化逻辑
172
- }
173
- }
174
-
175
- function DataComponent() {
176
- const service = useModel(DataService, []);
177
- const { globalLoading } = useModel(loader, []);
178
-
179
- return (
180
- <div>
181
- {globalLoading > 0 && <div>全局加载中...</div>}
182
- <button onClick={() => service.fetchData()}>加载数据</button>
183
- {service.data.map(item => (
184
- <div key={item.id}>{item.name}</div>
185
- ))}
186
- </div>
187
- );
188
- }
189
- ```
190
-
191
- ## API 参考
192
-
193
- ### 状态管理
194
-
195
- #### `useModel<T>(Constructor: T, args: ConstructorParameters<T>): InstanceType<T>`
196
-
197
- 创建或获取模型实例,自动观察状态变化并更新组件。
198
-
199
- **参数:**
200
-
201
- - `Constructor`: 模型类构造函数
202
- - `args`: 构造函数参数数组
203
-
204
- **返回值:** 模型实例,包含所有属性和方法
205
-
206
- #### `useInstance<T>(instance: T): T`
207
-
208
- 直接使用现有实例,自动观察状态变化。
209
-
210
- **参数:**
211
-
212
- - `instance`: 要观察的对象实例
213
-
214
- **返回值:** 观察后的实例
215
-
216
- #### `provide<T>(Constructor: T): T`
217
-
218
- 为类提供实例缓存和观察功能。
219
-
220
- **参数:**
221
-
222
- - `Constructor`: 要包装的类构造函数
223
-
224
- **返回值:** 包装后的构造函数,支持实例缓存
225
-
226
- ### IoC 容器
227
-
228
- #### `Container`
229
-
230
- IoC 容器组件,提供依赖注入的上下文环境。
231
-
232
- **Props:**
233
-
234
- - `namespace?: string` - 命名空间,用于隔离不同的依赖注入环境
235
- - `children: ReactNode` - 子组件
236
-
237
- #### `VInjection<T>`
238
-
239
- 值注入组件,将值注入到容器中。
240
-
241
- **Props:**
242
-
243
- - `schema: ZodType<T>` - Zod schema,用于类型验证
244
- - `val: T` - 要注入的值
245
-
246
- #### `CInjection<T>`
247
-
248
- 构造函数注入组件,将构造函数注入到容器中。
249
-
250
- **Props:**
251
-
252
- - `schema: ZodType<T>` - Zod schema,用于类型验证
253
- - `ctor: new () => T` - 要注入的构造函数
254
-
255
- #### `inject<T>(schema: ZodType<T>, namespace?: string)`
256
-
257
- 装饰器函数,用于从容器中注入依赖到类属性。
258
-
259
- **参数:**
260
-
261
- - `schema: ZodType<T>` - Zod schema,用于类型验证
262
- - `namespace?: string` - 可选的命名空间
263
-
264
- **返回值:** 装饰器函数
265
-
266
- ### 观察者模式
267
-
268
- #### `watch(target: object, callback: WatchCallback): () => void`
269
-
270
- 监听对象的变化。
271
-
272
- **参数:**
273
-
274
- - `target: object` - 要监听的对象
275
- - `callback: WatchCallback` - 变化时的回调函数
276
-
277
- **返回值:** 取消监听的函数
278
-
279
- **WatchCallback 类型:**
280
-
281
- ```typescript
282
- type WatchCallback = (
283
- path: Array<string | symbol>,
284
- oldValue: any,
285
- newValue: any
286
- ) => void;
287
- ```
288
-
289
- ### 加载器
290
-
291
- #### `loader.load(isGlobal?: boolean)`
292
-
293
- 异步方法装饰器,自动管理加载状态。
294
-
295
- **参数:**
296
-
297
- - `isGlobal?: boolean` - 是否影响全局加载状态,默认为 false
298
-
299
- **返回值:** 方法装饰器
300
-
301
- #### `loader.once()`
302
-
303
- 确保异步方法只执行一次的装饰器。
304
-
305
- **返回值:** 方法装饰器
306
-
307
- #### `loader.isLoading(target: Function): boolean`
308
-
309
- 检查指定方法是否正在加载中。
310
-
311
- **参数:**
312
-
313
- - `target: Function` - 要检查的方法
314
-
315
- **返回值:** 是否正在加载
316
-
317
- #### `loader.isGlobalLoading: boolean`
318
-
319
- 获取全局加载状态。
320
-
321
- **返回值:** 是否有全局加载正在进行
322
-
323
- #### `loader.globalLoading: number`
324
-
325
- 获取当前全局加载的数量。
326
-
327
- **返回值:** 正在进行的全局加载数量
328
-
329
- #### `finalizationRegistry(model: object)`
330
-
331
- 为模型注册垃圾回收回调。
332
-
333
- **参数:**
334
-
335
- - `model: object` - 要监听垃圾回收的对象
336
-
337
- **返回值:** 包含 `register` 和 `unregister` 方法的对象
338
-
339
- ## 高级用法
340
-
341
- ### 组件间通信
342
-
343
- ```tsx
344
- // 全局状态模型
345
- class AppState {
346
- user: User | null = null;
347
- theme: "light" | "dark" = "light";
348
-
349
- setUser(user: User) {
350
- this.user = user;
351
- }
352
-
353
- toggleTheme() {
354
- this.theme = this.theme === "light" ? "dark" : "light";
355
- }
356
- }
357
-
358
- // 在任何组件中使用
359
- function Header() {
360
- const { user, theme, toggleTheme } = useModel(AppState, []);
361
-
362
- return (
363
- <header className={theme}>
364
- <span>欢迎, {user?.name}</span>
365
- <button onClick={toggleTheme}>切换主题</button>
366
- </header>
367
- );
368
- }
369
-
370
- function Sidebar() {
371
- const { user } = useModel(AppState, []); // 同一个实例
372
-
373
- return <aside>{user && <UserProfile user={user} />}</aside>;
374
- }
375
- ```
376
-
377
- ### 嵌套模型
378
-
379
- ```tsx
380
- class UserModel {
381
- constructor(public id: string) {}
382
-
383
- name = "";
384
- email = "";
385
-
386
- updateProfile(data: Partial<UserModel>) {
387
- Object.assign(this, data);
388
- }
389
- }
390
-
391
- const User = provide(UserModel);
392
-
393
- class TeamModel {
394
- members: UserModel[] = [];
395
-
396
- addMember(userId: string) {
397
- const user = User(userId);
398
- this.members.push(user);
399
- }
400
-
401
- removeMember(userId: string) {
402
- this.members = this.members.filter(m => m.id !== userId);
403
- }
404
- }
405
-
406
- function TeamManagement() {
407
- const team = useModel(TeamModel, ["team-1"]);
408
-
409
- return (
410
- <div>
411
- <h2>团队成员</h2>
412
- {team.members.map(member => (
413
- <UserCard key={member.id} id={member.id} />
414
- ))}
415
- </div>
416
- );
417
- }
418
-
419
- function UserCard({ id }: { id: UserModel["id"] }) {
420
- const observedUser = useModel(User, [id]);
421
-
422
- return (
423
- <div>
424
- <span>{observedUser.name}</span>
425
- <span>{observedUser.email}</span>
426
- </div>
427
- );
428
- }
429
- ```
430
-
431
- ### 命名空间隔离
432
-
433
- ```tsx
434
- function App() {
435
- return (
436
- <div>
437
- <Container namespace="dev">
438
- <VInjection schema={configSchema} val={devConfig} />
439
- </Container>
440
-
441
- <Container namespace="prod">
442
- <VInjection schema={configSchema} val={prodConfig} />
443
- </Container>
444
- </div>
445
- );
446
- }
447
- ```
448
-
449
- ## 最佳实践
450
-
451
- ### 1. 模型设计
452
-
453
- - **单一职责**: 每个模型类应该只负责一个特定的业务领域
454
- - **类型安全**: 充分利用 TypeScript 的类型系统
455
-
456
- ```tsx
457
- // ✅ 好的实践
458
- class UserModel {
459
- constructor(public id: string) {}
460
-
461
- private _profile: UserProfile | null = null;
462
-
463
- get profile() {
464
- return this._profile;
465
- }
466
-
467
- updateProfile(updates: Partial<UserProfile>) {
468
- this._profile = { ...this._profile, ...updates };
469
- }
470
- }
471
-
472
- // ❌ 避免的实践
473
- class BadModel {
474
- // 避免在模型中直接操作 DOM
475
- updateUI() {
476
- document.getElementById("user-name")!.textContent = this.name;
477
- }
478
- }
479
- ```
480
-
481
- ### 2. 依赖注入
482
-
483
- - **明确依赖**: 使用 Zod schema 明确定义依赖的类型
484
- - **命名空间**: 使用命名空间隔离不同环境的配置
485
- - **懒加载**: 只在需要时注入依赖
486
-
487
- ### 3. 性能优化
488
-
489
- - **合理使用参数**: 相同参数会返回相同实例,利用这一点进行优化
490
- - **避免过度观察**: 不要观察不必要的对象
491
- - **及时清理**: 使用 `finalizationRegistry` 进行资源清理
492
-
493
- ## 类型定义
494
-
495
- ```typescript
496
- // 主要导出的类型
497
- export interface WatchCallback {
498
- (path: Array<string | symbol>, oldValue: any, newValue: any): void;
499
- }
500
-
501
- export interface FinalizationRegistry {
502
- register(callback: () => void): void;
503
- unregister(): void;
504
- }
505
-
506
- export interface LoaderInstance {
507
- loading: Record<symbol, [Function, Promise<unknown>]>;
508
- globalLoading: number;
509
- load(isGlobal?: boolean): MethodDecorator;
510
- once(): MethodDecorator;
511
- }
512
- ```
513
-
514
- ## 兼容性
515
-
516
- - **React**: >= 17.0.0
517
- - **TypeScript**: >= 4.5.0
518
- - **Zod**: ^4.1.5
519
-
520
- ## 许可证
521
-
522
- MIT License - 详见 [LICENSE.md](./LICENSE.md)
523
-
524
- ## 贡献
525
-
526
- 欢迎提交 Issue 和 Pull Request!
527
-
528
- ## 更新日志
529
-
530
- 详见 [CHANGELOG.md](./CHANGELOG.md)
1
+ # easy-model
2
+
3
+ **easy-model** 是一个围绕「类模型(Model Class)+ 依赖注入 + 精细化变更监听」构建的 React 状态管理与 IoC 工具集。你可以用普通的 TypeScript 类描述业务模型,通过少量 API 即可:
4
+
5
+ - **在函数组件中直接创建 / 注入模型实例**(`useModel` / `useInstance`)
6
+ - **跨组件共享同一实例**,支持按参数分组实例缓存(`provide`)
7
+ - **监听模型及其嵌套属性的变化**(`watch` / `useWatcher`)
8
+ - **用装饰器和 IoC 容器做依赖注入**(`Container` / `CInjection` / `VInjection` / `inject`)
9
+ - **统一管理异步调用的加载状态**(`loader` / `useLoader`)
10
+
11
+ 相比 Redux / MobX / Zustand,easy-model 的目标是:**保持接近类 OOP 的心智模型,同时提供较好的性能和类型体验,并内建 IoC 能力。**
12
+
13
+ ### 核心特性一览
14
+
15
+ - **类模型驱动(Class-based Model)**
16
+ 直接用 TypeScript 类描述业务:字段即状态,方法即业务逻辑,没有额外的 action / reducer ceremony。
17
+
18
+ - **按参数缓存实例(Instance by arguments)**
19
+ 使用 `provide` 包装后,相同参数获取的是同一实例,不同参数获取不同实例,天然支持「按业务 key」分区的状态。
20
+
21
+ - **深层变更监听(Deep watch)**
22
+ `watch` / `useWatcher` 可以监听到:
23
+ - 模型自身字段的变化;
24
+ - 嵌套对象属性变化;
25
+ - 实例之间的嵌套 / 引用关系变化;
26
+ - getter 返回的衍生实例的变化。
27
+
28
+ - **React Hooks 友好**
29
+ - `useModel`:在组件中创建并订阅模型;
30
+ - `useInstance`:在组件中订阅已有实例;
31
+ - `useWatcher`:在函数组件中挂载监听回调;
32
+ - `useLoader`:统一获取全局 loading 状态及单个方法的 loading 状态。
33
+
34
+ - **IoC 容器与依赖注入**
35
+ - 使用 `Container` / `CInjection` / `VInjection` / `config` 配置注入;
36
+ - 使用 `inject` 装饰器在类中按 schema 声明依赖;
37
+ - 支持 namespace 隔离与 `clearNamespace` 清理。
38
+
39
+ ### 示例(example/)
40
+
41
+ `example/` 目录包含运行时示例,可帮助快速理解 API。下面是几个「直给」的核心用法片段。
42
+
43
+ - **基础计数器:`useModel` / `useWatcher`**(见 `example/index.tsx`)
44
+ 使用 `CounterModel` 展示如何在函数组件中创建模型实例、读取 / 更新字段,并监听变更:
45
+
46
+ ```tsx
47
+ import { useModel, useWatcher } from "easy-model";
48
+
49
+ class CounterModel {
50
+ count = 0;
51
+ label: string;
52
+ constructor(initial = 0, label = "计数器") {
53
+ this.count = initial;
54
+ this.label = label;
55
+ }
56
+ increment() {
57
+ this.count += 1;
58
+ }
59
+ decrement() {
60
+ this.count -= 1;
61
+ }
62
+ }
63
+
64
+ function Counter() {
65
+ const counter = useModel(CounterModel, [0, "示例"]);
66
+
67
+ useWatcher(counter, (keys, prev, next) => {
68
+ console.log("changed:", keys.join("."), prev, "->", next);
69
+ });
70
+
71
+ return (
72
+ <div>
73
+ <h2>{counter.label}</h2>
74
+ <div>{counter.count}</div>
75
+ <button onClick={() => counter.decrement()}>-</button>
76
+ <button onClick={() => counter.increment()}>+</button>
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+
82
+ - **跨组件通信:`useModel` + `useInstance`**
83
+ 通过 `CommunicateModel` + `provide`,让多个组件共享同一实例(按参数分组):
84
+
85
+ ```tsx
86
+ import { provide, useModel, useInstance } from "easy-model";
87
+
88
+ class CommunicateModel {
89
+ constructor(public name: string) {}
90
+ value = 0;
91
+ random() {
92
+ this.value = Math.random();
93
+ }
94
+ }
95
+
96
+ const CommunicateProvider = provide(CommunicateModel);
97
+
98
+ function CommunicateA() {
99
+ const { value, random } = useModel(CommunicateModel, ["channel"]);
100
+ return (
101
+ <div>
102
+ <span>组件 A:{value}</span>
103
+ <button onClick={random}>改变数值</button>
104
+ </div>
105
+ );
106
+ }
107
+
108
+ function CommunicateB() {
109
+ const { value } = useInstance(CommunicateProvider("channel"));
110
+ return <div>组件 B:{value}</div>;
111
+ }
112
+ ```
113
+
114
+ - **独立监听:`watch`**
115
+ React 外部或普通函数中监听某个实例,将变更记录到日志列表中:
116
+
117
+ ```tsx
118
+ import { provide, watch } from "easy-model";
119
+
120
+ class WatchModel {
121
+ constructor(public name: string) {}
122
+ value = 0;
123
+ }
124
+
125
+ const WatchProvider = provide(WatchModel);
126
+ const inst = WatchProvider("watch-demo");
127
+
128
+ const stop = watch(inst, (keys, prev, next) => {
129
+ console.log(`${keys.join(".")}: ${prev} -> ${next}`);
130
+ });
131
+
132
+ inst.value += 1;
133
+ // 不再需要时取消监听
134
+ stop();
135
+ ```
136
+
137
+ - **异步加载与全局 Loading:`loader` / `useLoader`**
138
+ 通过装饰器标记异步方法,并在组件中读取全局 / 单方法 loading 状态:
139
+
140
+ ```tsx
141
+ import { loader, useLoader, useModel } from "easy-model";
142
+
143
+ class LoaderModel {
144
+ constructor(public name: string) {}
145
+
146
+ @loader.load(true)
147
+ async fetch() {
148
+ return new Promise<number>(resolve =>
149
+ setTimeout(() => resolve(42), 1000)
150
+ );
151
+ }
152
+ }
153
+
154
+ function LoaderDemo() {
155
+ const { isGlobalLoading, isLoading } = useLoader();
156
+ const inst = useModel(LoaderModel, ["loader-demo"]);
157
+
158
+ return (
159
+ <div>
160
+ <div>全局加载状态:{String(isGlobalLoading)}</div>
161
+ <div>当前加载状态:{String(isLoading(inst.fetch))}</div>
162
+ <button onClick={() => inst.fetch()} disabled={isGlobalLoading}>
163
+ 触发一次异步加载
164
+ </button>
165
+ </div>
166
+ );
167
+ }
168
+ ```
169
+
170
+ - **依赖注入示例:`example/inject.tsx`**
171
+ 展示了如何用 zod schema 描述依赖,并通过容器完成注入:
172
+
173
+ ```tsx
174
+ import {
175
+ CInjection,
176
+ Container,
177
+ VInjection,
178
+ config,
179
+ inject,
180
+ } from "easy-model";
181
+ import { object, number } from "zod";
182
+
183
+ const schema = object({ number: number() }).describe("测试用schema");
184
+
185
+ class Test {
186
+ xxx = 1;
187
+ }
188
+
189
+ class MFoo {
190
+ @inject(schema)
191
+ bar?: { number: number };
192
+ baz?: number;
193
+ }
194
+
195
+ config(
196
+ <Container>
197
+ <CInjection schema={schema} ctor={Test} />
198
+ <VInjection schema={schema} val={{ number: 100 }} />
199
+ </Container>
200
+ );
201
+ ```
202
+
203
+ - **Benchmark 示例:`example/benchmark.tsx`**
204
+ 提供 easy-model / Redux / MobX / Zustand 在同一场景下的**粗略性能对比面板**,下文有详细说明。
205
+
206
+ ### 测试(test/)
207
+
208
+ `test/` 目录使用 Vitest + React Testing Library 覆盖了核心行为:
209
+
210
+ - **provide 与实例缓存**
211
+ - 相同参数返回同一实例;
212
+ - 不同参数返回不同实例。
213
+
214
+ - **watch 的深层监听能力**
215
+ - 监听简单字段变更;
216
+ - 监听嵌套对象属性变更;
217
+ - 处理实例之间的嵌套引用关系;
218
+ - 支持 getter 返回实例(如 `child2`)的变更路径监听。
219
+
220
+ - **IoC 配置与命名空间**
221
+ - 通过 `config` + `Container` + `CInjection` / `VInjection` 注册依赖;
222
+ - `isRegistered` 判断指定 schema 是否已在某个 namespace 注册;
223
+ - `clearNamespace` 清理命名空间中的注册项。
224
+
225
+ - **Hooks 行为**
226
+ - `useModel` + `useInstance` 在两个组件间共享状态并同步更新 UI;
227
+ - `useWatcher` 在函数组件中监听模型变化;
228
+ - `loader` + `useLoader` 正确反映全局 / 单个方法的 loading 状态。
229
+
230
+ ### 与 Redux / MobX / Zustand 的对比
231
+
232
+ 下表是从「心智模型 / 用法复杂度 / 性能 & 能力边界」等角度对比 easy-model 与常见方案:
233
+
234
+ | 方案 | 编程模型 | 典型心智负担 | 内建 IoC / DI | 性能特征(本项目场景) |
235
+ | -------------- | ------------------------ | ------------------------------------------------------------------ | ---------------------- | --------------------------------------- |
236
+ | **easy-model** | 类模型 + Hooks + IoC | 写类 + 写方法即可,少量 API(`provide` / `useModel` / `watch` 等) | 是 | 在极端批量更新下仍为**个位数毫秒** |
237
+ | **Redux** | 不可变 state + reducer | 需要 action / reducer / dispatch 等模板代码 | 否 | 在同场景下为**数十毫秒级** |
238
+ | **MobX** | 可观察对象 + 装饰器 | 对响应式系统有一定学习成本,隐藏的依赖追踪 | 否(偏响应式而非 IoC) | 性能优于 Redux,但仍是**十几毫秒级** |
239
+ | **Zustand** | Hooks store + 函数式更新 | API 简洁,偏轻量,适合局部状态 | 否 | 在本场景下是**最快**,但不提供 IoC 能力 |
240
+
241
+ 从项目角度看,easy-model 的特点在于:
242
+
243
+ - **对比 Redux**:
244
+ - 不需要拆分 action / reducer / selector,业务逻辑直接写在模型方法里;
245
+ - 避免大量模板代码,类型推断更直接(基于类字段和方法签名);
246
+ - 自动处理实例缓存与变更订阅,无需手动 connect / useSelector。
247
+
248
+ - **对比 MobX**:
249
+ - 保留类模型的直觉优势,同时用显式 API(`watch` / `useWatcher`)暴露依赖关系;
250
+ - 依赖注入、命名空间、清理等能力是 easy-model 的一等公民,而非额外工具。
251
+
252
+ - **对比 Zustand**:
253
+ - 性能接近(在本项目 benchmark easy-model 仍处于个位数毫秒),但提供更完整的 IoC / DI / deep watch 组合能力;
254
+ - 更适合中大型、需要明确领域模型和依赖关系的项目,而不仅是「轻量局部状态 store」。
255
+
256
+ ### Benchmark 场景说明(与 Redux / MobX / Zustand 的粗略性能对比)
257
+
258
+ 在 `example/benchmark.tsx` 中,项目提供了一个**简单且偏极端**的 benchmark,用来在同一台机器上粗略比较不同状态管理方案在「大量同步写入」场景下的表现。核心场景为:
259
+
260
+ 1. **初始化一个包含 10,000 个数字的数组**;
261
+ 2. 点击按钮后,对所有元素做 **5 轮自增**;
262
+ 3. 使用 `performance.now()` 统计这段**同步计算与状态写入**时间;
263
+ 4. **不计入 React 首屏渲染时间**,仅关注每次点击触发的计算 + 状态写入耗时。
264
+
265
+ 在一台常规开发机上的一次测试结果(单位:ms,取单次运行的代表值)大致如下(数值可能因环境而异,仅供参考):
266
+
267
+ | 实现 | 耗时(ms) |
268
+ | -------------- | ---------- |
269
+ | **easy-model** | ≈ 3.1 |
270
+ | **Redux** | ≈ 51.5 |
271
+ | **MobX** | ≈ 16.9 |
272
+ | **Zustand** | ≈ 0.6 |
273
+
274
+ 需要特别强调:
275
+
276
+ - **这是一个刻意放大的「批量更新」场景**,主要目的是放大不同实现之间在「大量同步写入 + 通知」路径上的差异;
277
+ - 结果会受到:浏览器 / Node 版本、硬件性能、打包模式等多种因素影响,因此这里只能作为**趋势性的参考**,而非严谨的性能报告;
278
+ - Zustand 在这个场景下表现最好,这是符合其「极简 store + 函数式更新」定位的;
279
+ - easy-model 虽然在这类极端场景下略慢于 Zustand,但**仍明显快于 Redux / MobX**,同时提供:
280
+ - 类模型 + IoC + 深度监听等高级能力;
281
+ - 更适合中大型业务的结构化编码体验。
282
+
283
+ ### 适用场景
284
+
285
+ easy-model 更适合以下类型的项目:
286
+
287
+ - 领域模型清晰、希望用类来承载业务状态与方法;
288
+ - 需要在各处优雅地做依赖注入(如仓储、服务、配置、schema 等);
289
+ - 对「监听某个模型 / 某个嵌套字段的变化」有较强需求;
290
+ - 希望在保证结构化代码与可维护性的同时,获得接近轻量状态库的性能。
291
+
292
+ 如果你目前在使用 Redux / MobX / Zustand,想要:
293
+
294
+ - 减少心智负担和模板代码;
295
+ - 获得更自然的类模型与 IoC 能力;
296
+ - 又不希望在性能上明显吃亏;
297
+
298
+ 那么可以尝试将部分模块迁移到 easy-model,并先在 `example/benchmark.tsx` 中实际运行一次 benchmark,观察在你的机器和真实业务数据规模下的表现。