@e7w/easy-model 0.1.6 → 0.1.8

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,533 +1,233 @@
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
- class TeamModel {
392
- members: UserModel[] = [];
393
-
394
- addMember(userId: string) {
395
- const user = provide(UserModel)(userId);
396
- this.members.push(user);
397
- }
398
-
399
- removeMember(userId: string) {
400
- this.members = this.members.filter(m => m.id !== userId);
401
- }
402
- }
403
-
404
- function TeamManagement() {
405
- const team = useModel(TeamModel, ["team-1"]);
406
-
407
- return (
408
- <div>
409
- <h2>团队成员</h2>
410
- {team.members.map(member => (
411
- <UserCard key={member.id} user={member} />
412
- ))}
413
- </div>
414
- );
415
- }
416
-
417
- function UserCard({ user }: { user: UserModel }) {
418
- const observedUser = useInstance(user);
419
-
420
- return (
421
- <div>
422
- <span>{observedUser.name}</span>
423
- <span>{observedUser.email}</span>
424
- </div>
425
- );
426
- }
427
- ```
428
-
429
- ### 命名空间隔离
430
-
431
- ```tsx
432
- function App() {
433
- return (
434
- <div>
435
- {/* 开发环境配置 */}
436
- <Container namespace="dev">
437
- <VInjection schema={configSchema} val={devConfig} />
438
- <DevTools />
439
- </Container>
440
-
441
- {/* 生产环境配置 */}
442
- <Container namespace="prod">
443
- <VInjection schema={configSchema} val={prodConfig} />
444
- <MainApp />
445
- </Container>
446
- </div>
447
- );
448
- }
449
- ```
450
-
451
- ## 最佳实践
452
-
453
- ### 1. 模型设计
454
-
455
- - **单一职责**: 每个模型类应该只负责一个特定的业务领域
456
- - **不可变更新**: 尽量使用不可变的方式更新状态
457
- - **类型安全**: 充分利用 TypeScript 的类型系统
458
-
459
- ```tsx
460
- // ✅ 好的实践
461
- class UserModel {
462
- constructor(public id: string) {}
463
-
464
- private _profile: UserProfile | null = null;
465
-
466
- get profile() {
467
- return this._profile;
468
- }
469
-
470
- updateProfile(updates: Partial<UserProfile>) {
471
- this._profile = { ...this._profile, ...updates };
472
- }
473
- }
474
-
475
- // ❌ 避免的实践
476
- class BadModel {
477
- // 避免在模型中直接操作 DOM
478
- updateUI() {
479
- document.getElementById("user-name")!.textContent = this.name;
480
- }
481
- }
482
- ```
483
-
484
- ### 2. 依赖注入
485
-
486
- - **明确依赖**: 使用 Zod schema 明确定义依赖的类型
487
- - **命名空间**: 使用命名空间隔离不同环境的配置
488
- - **懒加载**: 只在需要时注入依赖
489
-
490
- ### 3. 性能优化
491
-
492
- - **合理使用参数**: 相同参数会返回相同实例,利用这一点进行优化
493
- - **避免过度观察**: 不要观察不必要的对象
494
- - **及时清理**: 使用 `finalizationRegistry` 进行资源清理
495
-
496
- ## 类型定义
497
-
498
- ```typescript
499
- // 主要导出的类型
500
- export interface WatchCallback {
501
- (path: Array<string | symbol>, oldValue: any, newValue: any): void;
502
- }
503
-
504
- export interface FinalizationRegistry {
505
- register(callback: () => void): void;
506
- unregister(): void;
507
- }
508
-
509
- export interface LoaderInstance {
510
- loading: Record<symbol, [Function, Promise<unknown>]>;
511
- globalLoading: number;
512
- load(isGlobal?: boolean): MethodDecorator;
513
- once(): MethodDecorator;
514
- }
515
- ```
516
-
517
- ## 兼容性
518
-
519
- - **React**: >= 17.0.0
520
- - **TypeScript**: >= 4.5.0
521
- - **Zod**: ^4.1.5
522
-
523
- ## 许可证
524
-
525
- MIT License - 详见 [LICENSE.md](./LICENSE.md)
526
-
527
- ## 贡献
528
-
529
- 欢迎提交 Issue 和 Pull Request!
530
-
531
- ## 更新日志
532
-
533
- 详见 [CHANGELOG.md](./CHANGELOG.md)
1
+ # easy-model
2
+
3
+ 轻量级的 React 状态管理与依赖注入库。
4
+
5
+ easy-model 专注于在 React 中提供简洁、高效的状态管理,减少样板代码并保持良好的可组合性。
6
+
7
+ ---
8
+
9
+ ## 特性
10
+
11
+ - 极简 API:基于 Hook 的使用方式,易上手、易迁移。
12
+ - 依赖注入:内置简单的容器,支持按类型/命名空间注入与清理。
13
+ - 响应式观察:`watch` 支持对对象变化进行监听并触发更新。
14
+ - Loader:统一的异步数据加载与缓存机制,搭配 `useLoader` 使用。
15
+ - 体积小、无侵入:适合大中小型项目或逐步引入到现有项目中。
16
+
17
+ ---
18
+
19
+ ## 安装
20
+
21
+ 推荐使用 `pnpm`:
22
+
23
+ ```bash
24
+ pnpm add @e7w/easy-model
25
+ ```
26
+
27
+ 或使用 `npm` / `yarn`:
28
+
29
+ ```bash
30
+ npm install @e7w/easy-model
31
+ yarn add @e7w/easy-model
32
+ ```
33
+
34
+ 开发(安装依赖并运行测试):
35
+
36
+ ```bash
37
+ pnpm install
38
+ pnpm test
39
+ ```
40
+
41
+ (如需启动示例项目,使用 Vite 的 `pnpm dev`,请参考 `package.json` 脚本。)
42
+
43
+ ---
44
+
45
+ ## 快速上手(React)
46
+
47
+ 下面演示核心用法,适合在函数组件中使用:
48
+
49
+ ```tsx
50
+ import React from "react";
51
+ import {
52
+ provide,
53
+ useModel,
54
+ loader,
55
+ useLoader,
56
+ inject,
57
+ watch,
58
+ } from "easy-model";
59
+
60
+ class Counter {
61
+ count = 0;
62
+ }
63
+
64
+ // 组件中读取并响应模型变化
65
+ function Counter() {
66
+ const model = useModel(Counter, []);
67
+ return (
68
+ <div>
69
+ <button onClick={() => model.count--}>-</button>
70
+ <span>{model.count}</span>
71
+ <button onClick={() => model.count++}>+</button>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ // 异步 loader 示例
77
+ class Photos {
78
+ @loader.load(true)
79
+ data = [];
80
+ async fetcher() {
81
+ const res = await fetch("/api/photos");
82
+ this.data = res.json();
83
+ }
84
+ }
85
+
86
+ function Gallery() {
87
+ const { data, fetcher } = useModel(Photos, []);
88
+ const { isGlobalLoading, isLoading } = useLoader();
89
+ useEffect(() => {
90
+ fetcher();
91
+ }, [fetcher]);
92
+ if (isGlobalLoading) return <div>全局loading</div>;
93
+ if (isLoading(fetcher)) return <div>本函数loading...</div>;
94
+ return (
95
+ <ul>
96
+ {data.map((p: any) => (
97
+ <li key={p.id}>{p.title}</li>
98
+ ))}
99
+ </ul>
100
+ );
101
+ }
102
+
103
+ // 依赖注入示例(类/服务)
104
+ const schema = object({
105
+ number: number(),
106
+ }).describe("测试用schema");
107
+ const schema2 = object({
108
+ xxx: number(),
109
+ }).describe("Test类的schema");
110
+
111
+ class Test {
112
+ xxx = 1;
113
+ }
114
+ class MFoo {
115
+ @inject(schema)
116
+ bar?: Infer<typeof schema>;
117
+ baz?: number;
118
+ @inject(schema2)
119
+ qux?: Infer<typeof schema2>;
120
+ }
121
+
122
+ config(
123
+ <>
124
+ <Container>
125
+ <CInjection schema={schema2} ctor={Test} />
126
+ <VInjection
127
+ schema={schema}
128
+ val={{
129
+ number: 100,
130
+ }}
131
+ />
132
+ </Container>
133
+ </>
134
+ );
135
+
136
+ const Foo = provide(MFoo);
137
+ let foo: MFoo | undefined = Foo();
138
+ foo.baz = 20;
139
+ document.writeln(JSON.stringify(foo.bar));
140
+ document.writeln(String(foo.baz));
141
+ document.writeln(JSON.stringify(foo.qux));
142
+ ```
143
+
144
+ 更多示例见项目自带的 [example](example).
145
+
146
+ ---
147
+
148
+ ## API(概览)
149
+
150
+ - `watch` — 响应式监听函数。
151
+ - `provide`, `finalizationRegistry` — 提供和清理模型/服务。
152
+ - `useModel`, `useInstance`, `useWatcher` — React Hook,用于绑定模型或自定义观察者。
153
+ - `loader`, `useLoader` 异步加载器与 Hook。
154
+ - `Container`, `CInjection`, `VInjection`, `inject`, `clearNamespace`, `isRegistered`, `config` — IoC/容器与注入相关工具。
155
+
156
+ ---
157
+
158
+ ## 与常见库对比(简要)
159
+
160
+ - Redux:
161
+ - Redux 适合大型、严格的全局状态管理,具有强大的中间件生态,但模板代码较多(actions/reducers)。
162
+ - easy-model 更轻量,基于对象与 Hook,适合快速开发,整合 DI 和 loader,减少样板代码。
163
+
164
+ - MobX:
165
+ - MobX 提供细粒度响应式,学习曲线较低。easy-model 提供类似的响应式监听器和 Hook,但 API 更显式,集成 DI 更方便。
166
+
167
+ - Zustand:
168
+ - Zustand 也很小巧,以 store 函数为中心。easy-model 的优势在于:内建依赖注入、loader 管理与更直接的对象模型(常见类/实例注入更友好)。
169
+
170
+ - React Context:
171
+ - Context 适合简单传递,性能需配合 memo/selector 优化。easy-model 提供专门的 Hook 与内部优化,减少组件重新渲染并支持按需注入。
172
+
173
+ 总的来说,easy-model 的优势可以概括为:
174
+
175
+ - **组合性强**:同一套模型既可以作为 React 状态容器,又可以作为依赖注入的服务,还可以挂载 loader / watch 等逻辑,减少在多个库之间来回穿梭。
176
+ - **API 简单、一致**:以“类 + Hook”为中心(`useModel` / `useInstance`),不需要额外的 action、reducer 或复杂的装饰器配置,迁移成本低。
177
+ - **性能可接受且易优化**:在典型的批量更新场景中,相比 Redux 有数量级的性能优势,同时通过内部的批量调度与轻量级响应式实现,保证在多数业务场景下不会成为性能瓶颈。
178
+ - **渐进式引入友好**:无需改造全局 store,可以从单个模块/页面开始使用模型类和 Hook,逐步替换原有状态管理方案。
179
+
180
+ ### 简单 benchmark 结论
181
+
182
+ 项目在 `example/benchmark.tsx` 中提供了一个**粗略的**对比示例,核心场景为:
183
+
184
+ - 初始化一个包含 10,000 个数字的数组;
185
+ - 点击按钮后,对所有元素做 5 轮自增;
186
+ - 使用 `performance.now()` 统计这段同步计算与状态写入时间(不计入 React 首屏渲染)。
187
+
188
+ 在一台常规开发机上的一次测试结果(单位:ms,取单次运行的代表值)大致如下:
189
+
190
+ | 实现 | 耗时(ms) | 说明 |
191
+ | ---------- | ---------- | ----------------------------------- |
192
+ | easy-model | ≈ 3.1 | 基于类实例 + `observe` 的响应式模型 |
193
+ | Redux | ≈ 51.5 | `createSlice` + Immer 不可变更新 |
194
+ | MobX | ≈ 16.9 | `makeAutoObservable` + observer |
195
+ | Zustand | 0.6 | 极简 store 函数实现 |
196
+
197
+ 从这个场景可以得到几个结论(仅作趋势参考):
198
+
199
+ - **对比 Redux**:easy-model 在该批量场景中大约比 Redux 快一个数量级(\~3ms vs \~50ms),同时完全避免了 action / reducer 等模板代码,更新路径更直接。
200
+ - **对比 MobX**:easy-model 与 MobX 属于同一数量级,前者以更简单的 Hook API + DI/loader 一体化为主打,后者在响应式生态上更成熟。
201
+ - **对比 Zustand**:Zustand 在这个极简数组场景下可以做到非常小的开销;easy-model 并不以“单一场景下绝对最快”为目标,而是在保持性能可接受的前提下,提供更丰富的能力组合(依赖注入、loader、watch 等)。
202
+
203
+ 整体来说:
204
+
205
+ - 在常见的批量更新场景里,**easy-model 相比 Redux 有明显的性能和开发体验优势**;
206
+ - MobX / Zustand 相比,easy-model 的优势更多体现在**组合能力与 API 一致性**:一套模型可以同时承担状态管理、依赖注入和异步加载,而不需要在多个库之间来回切换。
207
+
208
+ > 说明:该 benchmark 仅为示例级别,不是严谨的基准测试。不同设备、浏览器和实现细节都会显著影响具体数值,建议按需 clone 仓库后自行在本地运行 `pnpm dev`,并在示例页面的 “Benchmark” 区块里手动对比。
209
+
210
+ ---
211
+
212
+ ## 开发 & 测试
213
+
214
+ 运行测试:
215
+
216
+ ```bash
217
+ pnpm install
218
+ pnpm test
219
+ ```
220
+
221
+ 查看示例:打开 `example/index.tsx` 并参考 `vite`/`npm` 脚本运行本地示例(一般为 `pnpm dev`)。
222
+
223
+ ---
224
+
225
+ ## 贡献
226
+
227
+ 欢迎提交 issue 和 PR。请遵循项目的代码风格与测试约定。若要运行或修改示例,请先安装依赖并运行 `pnpm test` 验证现有用例。
228
+
229
+ ---
230
+
231
+ ## 许可证
232
+
233
+ MIT