@taicode/common-web 1.1.0 → 1.1.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.
Files changed (50) hide show
  1. package/output/cache-api/cache-api.d.ts +13 -0
  2. package/output/cache-api/cache-api.d.ts.map +1 -0
  3. package/output/cache-api/cache-api.js +114 -0
  4. package/output/cache-api/cache-api.test.d.ts +2 -0
  5. package/output/cache-api/cache-api.test.d.ts.map +1 -0
  6. package/output/cache-api/cache-api.test.js +348 -0
  7. package/output/cache-api/index.d.ts +2 -0
  8. package/output/cache-api/index.d.ts.map +1 -0
  9. package/output/cache-api/index.js +1 -0
  10. package/output/helpers/cache-api/cache-api.d.ts +13 -0
  11. package/output/helpers/cache-api/cache-api.d.ts.map +1 -0
  12. package/output/helpers/cache-api/cache-api.js +114 -0
  13. package/output/helpers/cache-api/cache-api.test.d.ts +2 -0
  14. package/output/helpers/cache-api/cache-api.test.d.ts.map +1 -0
  15. package/output/helpers/cache-api/cache-api.test.js +348 -0
  16. package/output/helpers/cache-api/index.d.ts +2 -0
  17. package/output/helpers/cache-api/index.d.ts.map +1 -0
  18. package/output/helpers/cache-api/index.js +1 -0
  19. package/output/helpers/side-cache/side-cache.d.ts +5 -2
  20. package/output/helpers/side-cache/side-cache.d.ts.map +1 -1
  21. package/output/helpers/side-cache/side-cache.js +41 -10
  22. package/output/helpers/side-cache/side-cache.test.js +166 -76
  23. package/output/service/index.d.ts +2 -0
  24. package/output/service/index.d.ts.map +1 -0
  25. package/output/service/index.js +1 -0
  26. package/output/service/service.d.ts +114 -0
  27. package/output/service/service.d.ts.map +1 -0
  28. package/output/service/service.js +189 -0
  29. package/output/service/service.test.d.ts +2 -0
  30. package/output/service/service.test.d.ts.map +1 -0
  31. package/output/service/service.test.jsx +367 -0
  32. package/output/side-cache/index.d.ts +2 -0
  33. package/output/side-cache/index.d.ts.map +1 -0
  34. package/output/side-cache/index.js +1 -0
  35. package/output/side-cache/side-cache.d.ts +10 -0
  36. package/output/side-cache/side-cache.d.ts.map +1 -0
  37. package/output/side-cache/side-cache.js +137 -0
  38. package/output/side-cache/side-cache.test.d.ts +2 -0
  39. package/output/side-cache/side-cache.test.d.ts.map +1 -0
  40. package/output/side-cache/side-cache.test.js +179 -0
  41. package/output/use-observer/index.d.ts +2 -0
  42. package/output/use-observer/index.d.ts.map +1 -0
  43. package/output/use-observer/index.js +1 -0
  44. package/output/use-observer/use-observer.d.ts +3 -0
  45. package/output/use-observer/use-observer.d.ts.map +1 -0
  46. package/output/use-observer/use-observer.js +16 -0
  47. package/output/use-observer/use-observer.test.d.ts +2 -0
  48. package/output/use-observer/use-observer.test.d.ts.map +1 -0
  49. package/output/use-observer/use-observer.test.jsx +134 -0
  50. package/package.json +2 -1
@@ -1,89 +1,179 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { SideCache } from './side-cache';
3
3
  describe('SideCache', () => {
4
- let sideCache;
4
+ let cache;
5
5
  beforeEach(() => {
6
- sideCache = new SideCache();
6
+ cache = new SideCache();
7
7
  });
8
- it('应该创建一个新的SideCache实例', () => {
9
- expect(sideCache).toBeInstanceOf(SideCache);
10
- expect(sideCache.value).toBeNull();
8
+ describe('初始状态', () => {
9
+ it('应该初始化为空状态', () => {
10
+ expect(cache.empty).toBe(true);
11
+ expect(cache.value).toBe(undefined);
12
+ });
11
13
  });
12
- it('应该处理异步函数并缓存结果', async () => {
13
- const mockFunc = vi.fn().mockResolvedValue('测试数据');
14
- const key = { id: 1, name: '测试键' };
15
- const result = await sideCache.handle(key, mockFunc);
16
- expect(result).toBe('测试数据');
17
- expect(sideCache.value).toBe('测试数据');
18
- expect(mockFunc).toHaveBeenCalledTimes(1);
14
+ describe('同步缓存', () => {
15
+ it('应该能够缓存同步函数的结果', () => {
16
+ const mockFunc = vi.fn(() => 'test-result');
17
+ const key = 'test-key';
18
+ const result = cache.handle(key, mockFunc);
19
+ expect(result).toBe('test-result');
20
+ expect(mockFunc).toHaveBeenCalledTimes(1);
21
+ expect(cache.empty).toBe(false);
22
+ expect(cache.value).toBe('test-result');
23
+ });
24
+ it('应该对相同 key 返回缓存的结果', () => {
25
+ const mockFunc = vi.fn(() => 'cached-result');
26
+ const key = 'same-key';
27
+ // 第一次调用
28
+ const result1 = cache.handle(key, mockFunc);
29
+ expect(result1).toBe('cached-result');
30
+ expect(mockFunc).toHaveBeenCalledTimes(1);
31
+ // 第二次调用相同 key,应该直接返回缓存
32
+ const result2 = cache.handle(key, mockFunc);
33
+ expect(result2).toBe('cached-result');
34
+ expect(mockFunc).toHaveBeenCalledTimes(2); // 函数仍会被调用,但会更新缓存
35
+ expect(cache.value).toBe('cached-result');
36
+ });
37
+ it('应该对不同 key 分别缓存', () => {
38
+ const mockFunc1 = vi.fn(() => 'result1');
39
+ const mockFunc2 = vi.fn(() => 'result2');
40
+ cache.handle('key1', mockFunc1);
41
+ expect(cache.value).toBe('result1');
42
+ cache.handle('key2', mockFunc2);
43
+ expect(cache.value).toBe('result2');
44
+ // 切换回 key1
45
+ cache.handle('key1', mockFunc1);
46
+ expect(cache.value).toBe('result1');
47
+ });
19
48
  });
20
- it('应该为不同的键缓存不同的值', async () => {
21
- const mockFunc1 = vi.fn().mockResolvedValue('数据1');
22
- const mockFunc2 = vi.fn().mockResolvedValue('数据2');
23
- const key1 = { id: 1 };
24
- const key2 = { id: 2 };
25
- await sideCache.handle(key1, mockFunc1);
26
- expect(sideCache.value).toBe('数据1');
27
- await sideCache.handle(key2, mockFunc2);
28
- expect(sideCache.value).toBe('数据2');
49
+ describe('异步缓存', () => {
50
+ it('应该能够缓存异步函数的结果', async () => {
51
+ const mockAsyncFunc = vi.fn(async () => 'async-result');
52
+ const key = 'async-key';
53
+ const promise = cache.handle(key, mockAsyncFunc);
54
+ expect(promise).toBeInstanceOf(Promise);
55
+ const result = await promise;
56
+ expect(result).toBe('async-result');
57
+ expect(mockAsyncFunc).toHaveBeenCalledTimes(1);
58
+ expect(cache.empty).toBe(false);
59
+ expect(cache.value).toBe('async-result');
60
+ });
61
+ it('应该在异步函数执行期间立即设置 currentKey', async () => {
62
+ const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
63
+ const mockAsyncFunc = vi.fn(async () => {
64
+ await delay(10);
65
+ return 'delayed-result';
66
+ });
67
+ const key = 'delayed-key';
68
+ // 在异步函数开始执行时,currentKey 应该已经设置
69
+ const promise = cache.handle(key, mockAsyncFunc);
70
+ // 此时异步函数还在执行,但 currentKey 已设置
71
+ // 如果之前有缓存,value 应该能立即获取到
72
+ const result = await promise;
73
+ expect(result).toBe('delayed-result');
74
+ expect(cache.value).toBe('delayed-result');
75
+ });
76
+ it('应该处理异步函数的错误', async () => {
77
+ const error = new Error('Async error');
78
+ const mockAsyncFunc = vi.fn(async () => {
79
+ throw error;
80
+ });
81
+ const key = 'error-key';
82
+ await expect(cache.handle(key, mockAsyncFunc)).rejects.toThrow('Async error');
83
+ expect(mockAsyncFunc).toHaveBeenCalledTimes(1);
84
+ });
29
85
  });
30
- it('应该在函数返回null时不缓存结果', async () => {
31
- const mockFunc = vi.fn().mockResolvedValue(null);
32
- const key = { id: 1 };
33
- const result = await sideCache.handle(key, mockFunc);
34
- expect(result).toBeNull();
35
- expect(sideCache.value).toBeNull();
86
+ describe('key 序列化', () => {
87
+ it('应该正确处理不同类型的 key', () => {
88
+ const mockFunc = vi.fn(() => 'result');
89
+ // 字符串 key
90
+ cache.handle('string-key', mockFunc);
91
+ expect(cache.value).toBe('result');
92
+ // 数字 key
93
+ cache.handle(123, mockFunc);
94
+ expect(cache.value).toBe('result');
95
+ // 对象 key
96
+ cache.handle({ id: 1, name: 'test' }, mockFunc);
97
+ expect(cache.value).toBe('result');
98
+ // 数组 key
99
+ cache.handle([1, 2, 3], mockFunc);
100
+ expect(cache.value).toBe('result');
101
+ });
102
+ it('相同内容的对象 key 应该被视为相同', () => {
103
+ const mockFunc = vi.fn(() => 'object-result');
104
+ const key1 = { id: 1, name: 'test' };
105
+ const key2 = { id: 1, name: 'test' };
106
+ cache.handle(key1, mockFunc);
107
+ const result1 = cache.value;
108
+ cache.handle(key2, mockFunc);
109
+ const result2 = cache.value;
110
+ expect(result1).toBe(result2);
111
+ expect(result1).toBe('object-result');
112
+ });
36
113
  });
37
- it('应该正确处理相同键的多次调用', async () => {
38
- const mockFunc = vi.fn().mockResolvedValue('缓存数据');
39
- const key = { id: 1, name: '相同键' };
40
- // 第一次调用
41
- await sideCache.handle(key, mockFunc);
42
- expect(sideCache.value).toBe('缓存数据');
43
- // 第二次调用相同键
44
- const mockFunc2 = vi.fn().mockResolvedValue('新数据');
45
- await sideCache.handle(key, mockFunc2);
46
- expect(sideCache.value).toBe('新数据');
47
- // 验证两个函数都被调用了
48
- expect(mockFunc).toHaveBeenCalledTimes(1);
49
- expect(mockFunc2).toHaveBeenCalledTimes(1);
114
+ describe('缓存时间戳', () => {
115
+ it('应该为缓存项添加创建时间', () => {
116
+ const mockFunc = vi.fn(() => 'timestamped-result');
117
+ const key = 'timestamp-key';
118
+ const beforeTime = new Date().toISOString();
119
+ cache.handle(key, mockFunc);
120
+ const afterTime = new Date().toISOString();
121
+ // 访问私有属性进行测试(仅用于测试目的)
122
+ const cacheData = cache.cache;
123
+ const cachedItem = cacheData[JSON.stringify(key)];
124
+ expect(cachedItem).toBeDefined();
125
+ expect(cachedItem.data).toBe('timestamped-result');
126
+ expect(cachedItem.createTime).toBeDefined();
127
+ expect(typeof cachedItem.createTime).toBe('string');
128
+ // 验证时间戳在合理范围内
129
+ expect(cachedItem.createTime >= beforeTime).toBe(true);
130
+ expect(cachedItem.createTime <= afterTime).toBe(true);
131
+ });
50
132
  });
51
- it('应该正确序列化复杂的键对象', async () => {
52
- const mockFunc = vi.fn().mockResolvedValue('复杂键数据');
53
- const complexKey = {
54
- nested: {
55
- array: [1, 2, 3],
56
- string: '测试',
57
- boolean: true
58
- }
59
- };
60
- await sideCache.handle(complexKey, mockFunc);
61
- expect(sideCache.value).toBe('复杂键数据');
62
- });
63
- it('应该处理异步函数抛出的错误', async () => {
64
- const mockFunc = vi.fn().mockRejectedValue(new Error('测试错误'));
65
- const key = { id: 1 };
66
- await expect(() => sideCache.handle(key, mockFunc)).rejects.toThrow('测试错误');
67
- expect(sideCache.value).toBeNull();
68
- });
69
- it('应该在没有设置当前键时返回null', () => {
70
- expect(sideCache.value).toBeNull();
133
+ describe('边界情况', () => {
134
+ it('应该处理 null undefined key', () => {
135
+ const mockFunc1 = vi.fn(() => 'null-key-result');
136
+ const mockFunc2 = vi.fn(() => 'undefined-key-result');
137
+ cache.handle(null, mockFunc1);
138
+ expect(cache.value).toBe('null-key-result');
139
+ cache.handle(undefined, mockFunc2);
140
+ expect(cache.value).toBe('undefined-key-result');
141
+ });
142
+ it('应该处理返回 null 或 undefined 的函数', () => {
143
+ const nullFunc = vi.fn(() => null);
144
+ const undefinedFunc = vi.fn(() => undefined);
145
+ cache.handle('null-key', nullFunc);
146
+ expect(cache.value).toBe(null);
147
+ expect(cache.empty).toBe(false);
148
+ cache.handle('undefined-key', undefinedFunc);
149
+ expect(cache.value).toBe(undefined);
150
+ expect(cache.empty).toBe(false);
151
+ });
152
+ it('应该处理返回 Promise.resolve(null) 的异步函数', async () => {
153
+ const nullAsyncFunc = vi.fn(async () => null);
154
+ const result = await cache.handle('async-null', nullAsyncFunc);
155
+ expect(result).toBe(null);
156
+ expect(cache.value).toBe(null);
157
+ expect(cache.empty).toBe(false);
158
+ });
71
159
  });
72
- it('应该处理数字类型的缓存数据', async () => {
73
- const numberCache = new SideCache();
74
- const mockFunc = vi.fn().mockResolvedValue(42);
75
- const key = 'number-key';
76
- const result = await numberCache.handle(key, mockFunc);
77
- expect(result).toBe(42);
78
- expect(numberCache.value).toBe(42);
79
- });
80
- it('应该处理对象类型的缓存数据', async () => {
81
- const objectCache = new SideCache();
82
- const testData = { id: 1, name: '测试对象' };
83
- const mockFunc = vi.fn().mockResolvedValue(testData);
84
- const key = 'object-key';
85
- const result = await objectCache.handle(key, mockFunc);
86
- expect(result).toEqual(testData);
87
- expect(objectCache.value).toEqual(testData);
160
+ describe('类型检查', () => {
161
+ it('应该正确识别 Promise-like 对象', async () => {
162
+ let thenCallback;
163
+ const promiseLike = {
164
+ then: vi.fn((callback) => {
165
+ thenCallback = callback;
166
+ return promiseLike;
167
+ })
168
+ };
169
+ const mockFunc = vi.fn(() => promiseLike);
170
+ const result = cache.handle('promise-like', mockFunc);
171
+ expect(result).toBe(promiseLike);
172
+ expect(promiseLike.then).toHaveBeenCalled();
173
+ // 模拟 Promise 完成
174
+ if (thenCallback) {
175
+ thenCallback('resolved-value');
176
+ }
177
+ });
88
178
  });
89
179
  });
@@ -0,0 +1,2 @@
1
+ export { useService, ServiceProvider } from './service';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1 @@
1
+ export { useService, ServiceProvider } from './service';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * React 服务层集成工具
3
+ *
4
+ * 本模块提供了在 React 应用中集成依赖注入(DI)和状态管理(Mobx)的工具函数和组件。
5
+ * 主要功能包括:
6
+ * - 通过 useService Hook 使用全局注册的服务,并自动响应状态变化
7
+ * - 通过 useLocalService Hook 创建局部服务实例,避免全局状态污染
8
+ * - 通过 ServiceProvider 组件提供服务容器上下文
9
+ * - 自动初始化服务实例
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // 1. 定义服务
14
+ * class UserService extends Service {
15
+ * @observable users: User[] = []
16
+ * async init() { return true }
17
+ * }
18
+ *
19
+ * // 2. 在根组件提供服务
20
+ * function App() {
21
+ * return (
22
+ * <ServiceProvider services={[UserService]}>
23
+ * <UserList />
24
+ * </ServiceProvider>
25
+ * )
26
+ * }
27
+ *
28
+ * // 3. 在组件中使用服务
29
+ * function UserList() {
30
+ * const users = useService(UserService, s => s.users)
31
+ * return <div>{users.map(user => <div key={user.id}>{user.name}</div>)}</div>
32
+ * }
33
+ * ```
34
+ */
35
+ import { Token, Container, Provider } from '@needle-di/core';
36
+ import React from 'react';
37
+ /**
38
+ * 使用全局注册的服务 Hook
39
+ *
40
+ * 该 Hook 从 DI 容器中获取指定的服务实例,并通过 selector 函数选择需要的数据。
41
+ * 当选择的数据发生变化时,组件会自动重新渲染。
42
+ *
43
+ * @template T 服务类型
44
+ * @template U 选择器返回的数据类型
45
+ * @param target 服务的 Token,用于从容器中获取服务实例
46
+ * @param selector 选择器函数,从服务实例中选择需要的数据
47
+ * @returns 选择器函数的返回值
48
+ *
49
+ * @throws {Error} 当组件不在 ServiceProvider 内部时抛出错误
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * // 获取用户列表
54
+ * const users = useService(UserService, service => service.users)
55
+ *
56
+ * // 获取当前用户信息
57
+ * const currentUser = useService(UserService, service => service.currentUser)
58
+ *
59
+ * // 获取用户数量
60
+ * const userCount = useService(UserService, service => service.users.length)
61
+ * ```
62
+ */
63
+ export declare function useService<T, U>(target: Token<T>, selector: (v: T) => U): U;
64
+ /**
65
+ * ServiceProvider 组件的属性接口
66
+ */
67
+ interface ServiceProviderProps {
68
+ /** 要注册到容器中的服务提供者数组 */
69
+ services: Provider<unknown>[];
70
+ /** 子组件 */
71
+ children: React.ReactNode;
72
+ }
73
+ /**
74
+ * 服务提供者组件
75
+ *
76
+ * 该组件为其子组件树提供依赖注入容器上下文。
77
+ * 它会自动复用父级容器(如果存在),并创建子容器来管理新的服务实例。
78
+ *
79
+ * 特性:
80
+ * - 自动复用父级容器,避免重复创建
81
+ * - 支持容器层级结构,子容器可以覆盖父容器的服务
82
+ * - 自动初始化实现了 Service 接口的服务
83
+ *
84
+ * @param props 组件属性
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * function App() {
89
+ * return (
90
+ * <ServiceProvider services={[UserService, ProductService]}>
91
+ * <Router>
92
+ * <Routes>
93
+ * <Route path="/users" element={<UserPage />} />
94
+ * <Route path="/products" element={<ProductPage />} />
95
+ * </Routes>
96
+ * </Router>
97
+ * </ServiceProvider>
98
+ * )
99
+ * }
100
+ *
101
+ * // 嵌套服务提供者
102
+ * function UserPage() {
103
+ * return (
104
+ * <ServiceProvider services={[LocalUserService]}>
105
+ * <UserList />
106
+ * </ServiceProvider>
107
+ * )
108
+ * }
109
+ * ```
110
+ */
111
+ /** 自动复用 Container,除非没有,否则不会重新创建 Container */
112
+ export declare function ServiceProvider(props: ServiceProviderProps): React.FunctionComponentElement<React.ProviderProps<Container | undefined>>;
113
+ export {};
114
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../source/service/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAA2D,MAAM,OAAO,CAAA;AAQ/E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAuB3E;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,sBAAsB;IACtB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAA;IAC7B,UAAU;IACV,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,6CAA6C;AAE7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,8EAuB1D"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * React 服务层集成工具
3
+ *
4
+ * 本模块提供了在 React 应用中集成依赖注入(DI)和状态管理(Mobx)的工具函数和组件。
5
+ * 主要功能包括:
6
+ * - 通过 useService Hook 使用全局注册的服务,并自动响应状态变化
7
+ * - 通过 useLocalService Hook 创建局部服务实例,避免全局状态污染
8
+ * - 通过 ServiceProvider 组件提供服务容器上下文
9
+ * - 自动初始化服务实例
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // 1. 定义服务
14
+ * class UserService extends Service {
15
+ * @observable users: User[] = []
16
+ * async init() { return true }
17
+ * }
18
+ *
19
+ * // 2. 在根组件提供服务
20
+ * function App() {
21
+ * return (
22
+ * <ServiceProvider services={[UserService]}>
23
+ * <UserList />
24
+ * </ServiceProvider>
25
+ * )
26
+ * }
27
+ *
28
+ * // 3. 在组件中使用服务
29
+ * function UserList() {
30
+ * const users = useService(UserService, s => s.users)
31
+ * return <div>{users.map(user => <div key={user.id}>{user.name}</div>)}</div>
32
+ * }
33
+ * ```
34
+ */
35
+ import { reaction, runInAction } from 'mobx';
36
+ import { catchIt } from '@taicode/common-base';
37
+ import { Container } from '@needle-di/core';
38
+ import React, { useContext, useEffect, useMemo, useState } from 'react';
39
+ /**
40
+ * React Context 用于传递 DI 容器实例
41
+ * 通过上下文在组件树中共享依赖注入容器
42
+ */
43
+ const ctx = React.createContext(undefined);
44
+ /**
45
+ * 使用全局注册的服务 Hook
46
+ *
47
+ * 该 Hook 从 DI 容器中获取指定的服务实例,并通过 selector 函数选择需要的数据。
48
+ * 当选择的数据发生变化时,组件会自动重新渲染。
49
+ *
50
+ * @template T 服务类型
51
+ * @template U 选择器返回的数据类型
52
+ * @param target 服务的 Token,用于从容器中获取服务实例
53
+ * @param selector 选择器函数,从服务实例中选择需要的数据
54
+ * @returns 选择器函数的返回值
55
+ *
56
+ * @throws {Error} 当组件不在 ServiceProvider 内部时抛出错误
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * // 获取用户列表
61
+ * const users = useService(UserService, service => service.users)
62
+ *
63
+ * // 获取当前用户信息
64
+ * const currentUser = useService(UserService, service => service.currentUser)
65
+ *
66
+ * // 获取用户数量
67
+ * const userCount = useService(UserService, service => service.users.length)
68
+ * ```
69
+ */
70
+ // DI + Mobx 自动
71
+ export function useService(target, selector) {
72
+ const container = useContext(ctx);
73
+ // 使用 useState 强制组件重新渲染
74
+ const [, refresh] = useState({});
75
+ React.useEffect(() => {
76
+ if (container == null)
77
+ return;
78
+ // 从容器中获取服务实例
79
+ const service = container.get(target);
80
+ // 设置 Mobx reaction,当 selector 返回的数据变化时触发重新渲染
81
+ return reaction(() => selector(service), () => refresh({}) // 触发组件重新渲染
82
+ );
83
+ }, [container, selector, target]);
84
+ if (container == null) {
85
+ throw new Error('Must be a child of ServiceProvider.');
86
+ }
87
+ return selector(container.get(target));
88
+ }
89
+ /**
90
+ * 服务提供者组件
91
+ *
92
+ * 该组件为其子组件树提供依赖注入容器上下文。
93
+ * 它会自动复用父级容器(如果存在),并创建子容器来管理新的服务实例。
94
+ *
95
+ * 特性:
96
+ * - 自动复用父级容器,避免重复创建
97
+ * - 支持容器层级结构,子容器可以覆盖父容器的服务
98
+ * - 自动初始化实现了 Service 接口的服务
99
+ *
100
+ * @param props 组件属性
101
+ *
102
+ * @example
103
+ * ```tsx
104
+ * function App() {
105
+ * return (
106
+ * <ServiceProvider services={[UserService, ProductService]}>
107
+ * <Router>
108
+ * <Routes>
109
+ * <Route path="/users" element={<UserPage />} />
110
+ * <Route path="/products" element={<ProductPage />} />
111
+ * </Routes>
112
+ * </Router>
113
+ * </ServiceProvider>
114
+ * )
115
+ * }
116
+ *
117
+ * // 嵌套服务提供者
118
+ * function UserPage() {
119
+ * return (
120
+ * <ServiceProvider services={[LocalUserService]}>
121
+ * <UserList />
122
+ * </ServiceProvider>
123
+ * )
124
+ * }
125
+ * ```
126
+ */
127
+ /** 自动复用 Container,除非没有,否则不会重新创建 Container */
128
+ // di 实现也有限制 https://needle-di.io/advanced/child-containers.html
129
+ export function ServiceProvider(props) {
130
+ const parentContainer = useContext(ctx);
131
+ const { children, services } = props;
132
+ // 创建子容器,复用父容器的服务配置
133
+ const container = useMemo(() => {
134
+ const currentContainer = new Container(parentContainer);
135
+ const typed = services;
136
+ currentContainer.bindAll(...typed); // 批量绑定服务到容器
137
+ // bindAll 要求参数必须是元组类型
138
+ return currentContainer;
139
+ }, [parentContainer, services]);
140
+ // 使用 React.createElement 避免将文件改为 .tsx
141
+ const init = React.createElement(InitService, { services, children });
142
+ return React.createElement(ctx.Provider, { value: container, children: container ? init : null });
143
+ }
144
+ /**
145
+ * 服务初始化组件
146
+ *
147
+ * 该组件负责自动初始化实现了 Service 接口的服务实例。
148
+ * 它会在组件挂载时检查每个服务是否需要初始化,并调用其 init 方法。
149
+ *
150
+ * 初始化流程:
151
+ * 1. 从容器中获取服务实例
152
+ * 2. 检查服务是否实现了 Service 接口(有 init 方法)
153
+ * 3. 检查服务是否已经初始化(inited 属性)
154
+ * 4. 如果未初始化,则调用 init 方法并更新状态
155
+ *
156
+ * @param props 组件属性,与 ServiceProviderProps 相同
157
+ */
158
+ function InitService(props) {
159
+ const { services, children } = props;
160
+ const container = useContext(ctx);
161
+ useEffect(() => {
162
+ if (container == null)
163
+ return;
164
+ if (services.length == 0)
165
+ return;
166
+ // 异步初始化所有服务
167
+ (async () => {
168
+ const initPromises = services.map(async (service) => {
169
+ // 尝试从容器中获取服务实例(可选的,避免未注册时报错)
170
+ const instance = container.get(service, { optional: true });
171
+ if (instance != null && typeof instance === 'object') {
172
+ // 检查实例是否实现了 Service 接口
173
+ if ('init' in instance && typeof instance.init === 'function') {
174
+ // 检查是否已经初始化
175
+ if (instance.inited == false) {
176
+ // 安全地调用 init 方法,捕获可能的异常
177
+ const result = await catchIt(() => instance.init());
178
+ // 使用 runInAction 确保状态更新被 Mobx 正确跟踪
179
+ runInAction(() => instance.inited = !result.isError());
180
+ }
181
+ }
182
+ }
183
+ });
184
+ // 等待所有服务初始化完成
185
+ await Promise.all(initPromises);
186
+ })();
187
+ }, [container, services]);
188
+ return children;
189
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.d.ts","sourceRoot":"","sources":["../../source/service/service.test.tsx"],"names":[],"mappings":""}