@taicode/common-web 1.1.5 → 1.1.7
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.
|
@@ -37,22 +37,34 @@ import React from 'react';
|
|
|
37
37
|
/**
|
|
38
38
|
* 使用全局注册的服务 Hook
|
|
39
39
|
*
|
|
40
|
-
* 该 Hook 从 DI
|
|
40
|
+
* 该 Hook 从 DI 容器中获取指定的服务实例,支持可选的 selector 函数来选择需要的数据。
|
|
41
41
|
* 当选择的数据发生变化时,组件会自动重新渲染。
|
|
42
42
|
*
|
|
43
|
+
* 特性:
|
|
44
|
+
* - 从全局容器获取服务实例
|
|
45
|
+
* - 支持 Mobx 响应式更新
|
|
46
|
+
* - 支持可选 selector,不传时监听整个服务的所有变化
|
|
47
|
+
* - 智能检测 selector 返回值,优化响应式监听策略
|
|
48
|
+
*
|
|
43
49
|
* @template T 服务类型
|
|
44
50
|
* @template U 选择器返回的数据类型
|
|
45
51
|
* @param target 服务的 Token,用于从容器中获取服务实例
|
|
46
|
-
* @param selector
|
|
47
|
-
* @returns
|
|
52
|
+
* @param selector 可选的选择器函数。如果不传入,返回服务实例并监听所有 observable 属性变化
|
|
53
|
+
* @returns 如果传入了 selector,返回选择器的结果;否则返回服务实例
|
|
48
54
|
*
|
|
49
55
|
* @throws {Error} 当组件不在 ServiceProvider 内部时抛出错误
|
|
50
56
|
*
|
|
51
57
|
* @example
|
|
52
58
|
* ```tsx
|
|
53
|
-
* //
|
|
59
|
+
* // 使用选择器,只监听特定属性
|
|
54
60
|
* const users = useService(UserService, service => service.users)
|
|
55
61
|
*
|
|
62
|
+
* // 不使用选择器,监听整个服务的所有变化
|
|
63
|
+
* const userService = useService(UserService)
|
|
64
|
+
*
|
|
65
|
+
* // 选择器返回服务实例本身(智能检测,自动优化响应式监听)
|
|
66
|
+
* const service = useService(UserService, s => s)
|
|
67
|
+
*
|
|
56
68
|
* // 获取当前用户信息
|
|
57
69
|
* const currentUser = useService(UserService, service => service.currentUser)
|
|
58
70
|
*
|
|
@@ -60,7 +72,83 @@ import React from 'react';
|
|
|
60
72
|
* const userCount = useService(UserService, service => service.users.length)
|
|
61
73
|
* ```
|
|
62
74
|
*/
|
|
63
|
-
export declare function useService<T
|
|
75
|
+
export declare function useService<T>(target: Token<T>): T;
|
|
76
|
+
export declare function useService<T, U>(target: Token<T>, selector: (service: T) => U): U;
|
|
77
|
+
/**
|
|
78
|
+
* 使用局部服务实例 Hook
|
|
79
|
+
*
|
|
80
|
+
* 该 Hook 创建指定服务的局部实例,既能注入全局依赖,又不会污染全局上下文。
|
|
81
|
+
* 局部实例在 Hook 的整个生命周期内保持引用稳定,支持自动初始化和响应式更新。
|
|
82
|
+
*
|
|
83
|
+
* 特性:
|
|
84
|
+
* - 创建局部服务实例,避免全局状态污染
|
|
85
|
+
* - 继承全局容器的所有依赖,支持依赖注入
|
|
86
|
+
* - 在组件重渲染时保持实例引用稳定
|
|
87
|
+
* - 自动初始化实现了 Service 接口的服务
|
|
88
|
+
* - 支持 Mobx 响应式更新
|
|
89
|
+
* - 支持可选 selector,不传时监听整个服务的所有变化
|
|
90
|
+
*
|
|
91
|
+
* @template T 服务类型
|
|
92
|
+
* @template U 选择器返回的数据类型
|
|
93
|
+
* @param ServiceClass 服务构造函数/类
|
|
94
|
+
* @param selector 可选的选择器函数。如果不传入,返回服务实例并监听所有 observable 属性变化
|
|
95
|
+
* @returns 如果传入了 selector,返回选择器的结果;否则返回服务实例
|
|
96
|
+
*
|
|
97
|
+
* @throws {Error} 当组件不在 ServiceProvider 内部时抛出错误
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* // 使用选择器,只监听特定属性
|
|
102
|
+
* const localUsers = useLocalService(LocalUserService, service => service.users)
|
|
103
|
+
*
|
|
104
|
+
* // 不使用选择器,监听整个服务的所有变化
|
|
105
|
+
* const service = useLocalService(LocalUserService)
|
|
106
|
+
*
|
|
107
|
+
* // 局部服务可以注入全局服务
|
|
108
|
+
* class LocalUserService extends Service {
|
|
109
|
+
* constructor(
|
|
110
|
+
* @inject(ApiService) private apiService: ApiService,
|
|
111
|
+
* @inject(ConfigService) private configService: ConfigService
|
|
112
|
+
* ) {
|
|
113
|
+
* super()
|
|
114
|
+
* }
|
|
115
|
+
*
|
|
116
|
+
* @observable users: User[] = []
|
|
117
|
+
*
|
|
118
|
+
* async init() {
|
|
119
|
+
* this.users = await this.apiService.getUsers()
|
|
120
|
+
* return true
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
*
|
|
124
|
+
* // 使用局部服务
|
|
125
|
+
* function UserComponent() {
|
|
126
|
+
* const users = useLocalService(LocalUserService, s => s.users)
|
|
127
|
+
* const userCount = useLocalService(LocalUserService, s => s.users.length)
|
|
128
|
+
*
|
|
129
|
+
* return (
|
|
130
|
+
* <div>
|
|
131
|
+
* <p>用户数量: {userCount}</p>
|
|
132
|
+
* {users.map(user => <div key={user.id}>{user.name}</div>)}
|
|
133
|
+
* </div>
|
|
134
|
+
* )
|
|
135
|
+
* }
|
|
136
|
+
*
|
|
137
|
+
* // 不使用选择器的例子
|
|
138
|
+
* function SimpleUserComponent() {
|
|
139
|
+
* const service = useLocalService(LocalUserService)
|
|
140
|
+
*
|
|
141
|
+
* return (
|
|
142
|
+
* <div>
|
|
143
|
+
* <p>用户数量: {service.users.length}</p>
|
|
144
|
+
* {service.users.map(user => <div key={user.id}>{user.name}</div>)}
|
|
145
|
+
* </div>
|
|
146
|
+
* )
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare function useLocalService<T>(ServiceClass: new (...args: any[]) => T): T;
|
|
151
|
+
export declare function useLocalService<T, U>(ServiceClass: new (...args: any[]) => T, selector: (service: T) => U): U;
|
|
64
152
|
/**
|
|
65
153
|
* ServiceProvider 组件的属性接口
|
|
66
154
|
*/
|
|
@@ -1 +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;
|
|
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;AA+D/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAClD,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAgDlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;AAC9E,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAyF9G;;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"}
|
|
@@ -33,41 +33,57 @@
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
import { reaction, runInAction } from 'mobx';
|
|
36
|
-
import { catchIt } from '@taicode/common-base';
|
|
37
36
|
import { Container } from '@needle-di/core';
|
|
38
|
-
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
37
|
+
import React, { useContext, useEffect, useMemo, useState, useRef } from 'react';
|
|
39
38
|
/**
|
|
40
39
|
* React Context 用于传递 DI 容器实例
|
|
41
40
|
* 通过上下文在组件树中共享依赖注入容器
|
|
42
41
|
*/
|
|
43
42
|
const ctx = React.createContext(undefined);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
43
|
+
const INIT_RETURN_ERROR_MESSAGE = 'Service init must return a boolean value.';
|
|
44
|
+
class InvalidInitReturnError extends Error {
|
|
45
|
+
constructor() {
|
|
46
|
+
super(INIT_RETURN_ERROR_MESSAGE);
|
|
47
|
+
this.name = 'InvalidInitReturnError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function setServiceInited(target, value) {
|
|
51
|
+
runInAction(() => {
|
|
52
|
+
if (target != null && typeof target === 'object') {
|
|
53
|
+
target.inited = value;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function applyInitResult(target, result) {
|
|
58
|
+
if (typeof result === 'boolean') {
|
|
59
|
+
setServiceInited(target, result);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setServiceInited(target, false);
|
|
63
|
+
throw new InvalidInitReturnError();
|
|
64
|
+
}
|
|
65
|
+
async function initializeServiceInstance(target) {
|
|
66
|
+
if (target == null ||
|
|
67
|
+
typeof target !== 'object' ||
|
|
68
|
+
!('init' in target) ||
|
|
69
|
+
typeof target.init !== 'function') {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const service = target;
|
|
73
|
+
if (service.inited === true) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const result = await service.init();
|
|
78
|
+
applyInitResult(service, result);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof InvalidInitReturnError) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
setServiceInited(service, false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
71
87
|
export function useService(target, selector) {
|
|
72
88
|
const container = useContext(ctx);
|
|
73
89
|
// 使用 useState 强制组件重新渲染
|
|
@@ -78,13 +94,98 @@ export function useService(target, selector) {
|
|
|
78
94
|
// 从容器中获取服务实例
|
|
79
95
|
const service = container.get(target);
|
|
80
96
|
// 设置 Mobx reaction,当 selector 返回的数据变化时触发重新渲染
|
|
81
|
-
|
|
97
|
+
const dispose = reaction(() => {
|
|
98
|
+
// 如果没有提供 selector,使用默认策略监听整个服务的变化
|
|
99
|
+
if (!selector) {
|
|
100
|
+
// 直接访问服务实例,让 Mobx 自动收集依赖
|
|
101
|
+
return service;
|
|
102
|
+
}
|
|
103
|
+
// 执行 selector 获取结果
|
|
104
|
+
const result = selector(service);
|
|
105
|
+
// 智能检测:如果 selector 返回的就是服务实例本身,
|
|
106
|
+
// 则切换到监听所有属性变化的模式
|
|
107
|
+
if (result === service) {
|
|
108
|
+
// 通过访问服务实例让 Mobx 收集所有 observable 属性的依赖
|
|
109
|
+
return service;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}, () => refresh({}) // 触发组件重新渲染
|
|
82
113
|
);
|
|
114
|
+
return dispose;
|
|
83
115
|
}, [container, selector, target]);
|
|
84
116
|
if (container == null) {
|
|
85
117
|
throw new Error('Must be a child of ServiceProvider.');
|
|
86
118
|
}
|
|
87
|
-
|
|
119
|
+
// 返回选择器结果或服务实例
|
|
120
|
+
const service = container.get(target);
|
|
121
|
+
return selector ? selector(service) : service;
|
|
122
|
+
}
|
|
123
|
+
export function useLocalService(ServiceClass, selector) {
|
|
124
|
+
const parentContainer = useContext(ctx);
|
|
125
|
+
// 使用 useRef 保持局部容器和服务实例的引用稳定
|
|
126
|
+
const localContainerRef = useRef(null);
|
|
127
|
+
const serviceInstanceRef = useRef(null);
|
|
128
|
+
// 使用 useState 强制组件重新渲染,完全模仿 useService
|
|
129
|
+
const [, refresh] = useState({});
|
|
130
|
+
const [initError, setInitError] = useState(null);
|
|
131
|
+
if (parentContainer == null) {
|
|
132
|
+
throw new Error('Must be a child of ServiceProvider.');
|
|
133
|
+
}
|
|
134
|
+
// 确保服务实例已创建
|
|
135
|
+
if (serviceInstanceRef.current == null) {
|
|
136
|
+
// 创建继承父容器的子容器
|
|
137
|
+
localContainerRef.current = new Container(parentContainer);
|
|
138
|
+
// 将服务类绑定到局部容器
|
|
139
|
+
localContainerRef.current.bindAll(ServiceClass);
|
|
140
|
+
// 获取服务实例
|
|
141
|
+
serviceInstanceRef.current = localContainerRef.current.get(ServiceClass);
|
|
142
|
+
}
|
|
143
|
+
// 模仿 useService 的结构:在 useEffect 中设置 reaction 和初始化
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
if (parentContainer == null)
|
|
146
|
+
return;
|
|
147
|
+
const service = serviceInstanceRef.current;
|
|
148
|
+
// 设置 Mobx reaction,当 selector 返回的数据变化时触发重新渲染
|
|
149
|
+
const dispose = reaction(() => {
|
|
150
|
+
// 如果没有提供 selector,使用默认策略监听整个服务的变化
|
|
151
|
+
if (!selector) {
|
|
152
|
+
// 直接访问服务实例,让 Mobx 自动收集依赖
|
|
153
|
+
return service;
|
|
154
|
+
}
|
|
155
|
+
// 执行 selector 获取结果
|
|
156
|
+
const result = selector(service);
|
|
157
|
+
// 智能检测:如果 selector 返回的就是服务实例本身,
|
|
158
|
+
// 则切换到监听所有属性变化的模式
|
|
159
|
+
if (result === service) {
|
|
160
|
+
// 通过访问服务实例让 Mobx 收集所有 observable 属性的依赖
|
|
161
|
+
return service;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}, () => refresh({}) // 触发组件重新渲染
|
|
165
|
+
);
|
|
166
|
+
return dispose;
|
|
167
|
+
}, [parentContainer, selector, ServiceClass]);
|
|
168
|
+
// 单独的 useEffect 处理服务初始化,避免与 selector 变化的冲突
|
|
169
|
+
React.useEffect(() => {
|
|
170
|
+
if (parentContainer == null)
|
|
171
|
+
return;
|
|
172
|
+
let disposed = false;
|
|
173
|
+
setInitError(prev => (prev === null ? prev : null));
|
|
174
|
+
const service = serviceInstanceRef.current;
|
|
175
|
+
initializeServiceInstance(service).catch((error) => {
|
|
176
|
+
if (error instanceof InvalidInitReturnError && !disposed) {
|
|
177
|
+
setInitError(error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return () => {
|
|
181
|
+
disposed = true;
|
|
182
|
+
};
|
|
183
|
+
}, [parentContainer, ServiceClass]); // 注意:这里不依赖 selector
|
|
184
|
+
if (initError) {
|
|
185
|
+
throw initError;
|
|
186
|
+
}
|
|
187
|
+
// 返回选择器结果或服务实例
|
|
188
|
+
return selector ? selector(serviceInstanceRef.current) : serviceInstanceRef.current;
|
|
88
189
|
}
|
|
89
190
|
/**
|
|
90
191
|
* 服务提供者组件
|
|
@@ -158,32 +259,38 @@ export function ServiceProvider(props) {
|
|
|
158
259
|
function InitService(props) {
|
|
159
260
|
const { services, children } = props;
|
|
160
261
|
const container = useContext(ctx);
|
|
262
|
+
const [initError, setInitError] = React.useState(null);
|
|
161
263
|
useEffect(() => {
|
|
162
264
|
if (container == null)
|
|
163
265
|
return;
|
|
164
266
|
if (services.length == 0)
|
|
165
267
|
return;
|
|
268
|
+
let disposed = false;
|
|
269
|
+
setInitError(prev => (prev === null ? prev : null));
|
|
166
270
|
// 异步初始化所有服务
|
|
167
|
-
(async () => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
runInAction(() => instance.inited = !result.isError());
|
|
180
|
-
}
|
|
271
|
+
const initPromises = services.map(async (service) => {
|
|
272
|
+
// 尝试从容器中获取服务实例(可选的,避免未注册时报错)
|
|
273
|
+
const instance = container.get(service, { optional: true });
|
|
274
|
+
if (instance != null && typeof instance === 'object') {
|
|
275
|
+
// 检查实例是否实现了 Service 接口
|
|
276
|
+
if ('init' in instance && typeof instance.init === 'function') {
|
|
277
|
+
if (instance.inited !== true) {
|
|
278
|
+
await initializeServiceInstance(instance).catch((error) => {
|
|
279
|
+
if (error instanceof InvalidInitReturnError && !disposed) {
|
|
280
|
+
setInitError(error);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
181
283
|
}
|
|
182
284
|
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
void Promise.all(initPromises);
|
|
288
|
+
return () => {
|
|
289
|
+
disposed = true;
|
|
290
|
+
};
|
|
187
291
|
}, [container, services]);
|
|
292
|
+
if (initError) {
|
|
293
|
+
throw initError;
|
|
294
|
+
}
|
|
188
295
|
return children;
|
|
189
296
|
}
|