@qlover/create-app 0.6.1 → 0.6.3
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/CHANGELOG.md +61 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/README.en.md +257 -0
- package/dist/templates/react-app/README.md +29 -231
- package/dist/templates/react-app/config/IOCIdentifier.ts +13 -0
- package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/en/development-guide.md +523 -0
- package/dist/templates/react-app/docs/en/env.md +482 -0
- package/dist/templates/react-app/docs/en/global.md +509 -0
- package/dist/templates/react-app/docs/en/i18n.md +268 -0
- package/dist/templates/react-app/docs/en/index.md +173 -0
- package/dist/templates/react-app/docs/en/ioc.md +424 -0
- package/dist/templates/react-app/docs/en/project-structure.md +434 -0
- package/dist/templates/react-app/docs/en/request.md +425 -0
- package/dist/templates/react-app/docs/en/router.md +404 -0
- package/dist/templates/react-app/docs/en/store.md +321 -0
- package/dist/templates/react-app/docs/en/theme.md +424 -0
- package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
- package/dist/templates/react-app/docs/zh/env.md +479 -0
- package/dist/templates/react-app/docs/zh/global.md +511 -0
- package/dist/templates/react-app/docs/zh/i18n.md +268 -0
- package/dist/templates/react-app/docs/zh/index.md +173 -0
- package/dist/templates/react-app/docs/zh/ioc.md +422 -0
- package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
- package/dist/templates/react-app/docs/zh/request.md +429 -0
- package/dist/templates/react-app/docs/zh/router.md +408 -0
- package/dist/templates/react-app/docs/zh/store.md +321 -0
- package/dist/templates/react-app/docs/zh/theme.md +424 -0
- package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
- package/dist/templates/react-app/package.json +2 -2
- package/dist/templates/react-app/src/base/apis/AiApi.ts +10 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -17
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +2 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +7 -5
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -2
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +33 -0
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +1 -1
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +2 -2
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +1 -1
- package/dist/templates/react-app/src/base/services/RouteService.ts +5 -2
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -10
- package/dist/templates/react-app/src/core/IOC.ts +73 -83
- package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +52 -4
- package/dist/templates/react-app/src/core/bootstraps/{index.ts → BootstrapsRegistry.ts} +2 -3
- package/dist/templates/react-app/src/core/registers/IocRegisterImpl.ts +25 -0
- package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +11 -17
- package/dist/templates/react-app/src/core/registers/RegisterControllers.ts +10 -4
- package/dist/templates/react-app/src/core/registers/RegisterGlobals.ts +6 -15
- package/dist/templates/react-app/src/main.tsx +2 -5
- package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
- package/dist/templates/react-app/src/styles/css/page.css +1 -1
- package/dist/templates/react-app/src/uikit/controllers/JSONStorageController.ts +1 -1
- package/dist/templates/react-app/tsconfig.app.json +2 -1
- package/dist/templates/react-app/tsconfig.node.json +2 -1
- package/package.json +2 -2
- package/dist/templates/react-app/src/base/port/ApiTransactionInterface.ts +0 -7
- package/dist/templates/react-app/src/base/port/RequestCatcherInterface.ts +0 -12
- package/dist/templates/react-app/src/core/bootstrap.ts +0 -58
- package/dist/templates/react-app/src/core/registers/RegisterApi.ts +0 -5
- package/dist/templates/react-app/src/core/registers/index.ts +0 -32
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# TypeScript 开发指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
本指南介绍项目中 TypeScript 的使用规范,重点强调三种编程范式:
|
|
6
|
+
|
|
7
|
+
- 面向对象编程(OOP)
|
|
8
|
+
- 面向接口编程(Interface-based)
|
|
9
|
+
- 面向配置编程(Configuration-based)
|
|
10
|
+
|
|
11
|
+
## 编程范式
|
|
12
|
+
|
|
13
|
+
### 1. 面向对象编程(OOP)
|
|
14
|
+
|
|
15
|
+
在项目中,我们大量使用面向对象的思想来组织代码,主要体现在:
|
|
16
|
+
|
|
17
|
+
#### 类的设计
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// 基类设计
|
|
21
|
+
abstract class StoreInterface<T extends StoreStateInterface> {
|
|
22
|
+
protected state: T;
|
|
23
|
+
|
|
24
|
+
constructor(initialState: T) {
|
|
25
|
+
this.state = initialState;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
abstract setState(state: Partial<T>): void;
|
|
29
|
+
|
|
30
|
+
getState(): T {
|
|
31
|
+
return this.state;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 具体实现
|
|
36
|
+
@injectable()
|
|
37
|
+
class UserStore extends StoreInterface<UserState> {
|
|
38
|
+
constructor() {
|
|
39
|
+
super({
|
|
40
|
+
user: null,
|
|
41
|
+
loading: false
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setState(state: Partial<UserState>): void {
|
|
46
|
+
this.state = { ...this.state, ...state };
|
|
47
|
+
this.notify();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private notify(): void {
|
|
51
|
+
// 通知观察者
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### 继承和多态
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// 基础服务接口
|
|
60
|
+
interface ServiceInterface {
|
|
61
|
+
initialize(): Promise<void>;
|
|
62
|
+
destroy(): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 抽象基类
|
|
66
|
+
abstract class BaseService implements ServiceInterface {
|
|
67
|
+
abstract initialize(): Promise<void>;
|
|
68
|
+
|
|
69
|
+
destroy(): void {
|
|
70
|
+
// 通用清理逻辑
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 具体服务实现
|
|
75
|
+
class UserService extends BaseService {
|
|
76
|
+
async initialize(): Promise<void> {
|
|
77
|
+
// 用户服务初始化逻辑
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 扩展特定方法
|
|
81
|
+
async login(): Promise<void> {
|
|
82
|
+
// 登录逻辑
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. 面向接口编程
|
|
88
|
+
|
|
89
|
+
项目强调使用接口来定义契约,实现松耦合:
|
|
90
|
+
|
|
91
|
+
#### 接口定义
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// 定义接口契约
|
|
95
|
+
interface StorageInterface<T> {
|
|
96
|
+
get(key: string): T | null;
|
|
97
|
+
set(key: string, value: T): void;
|
|
98
|
+
remove(key: string): void;
|
|
99
|
+
clear(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 实现接口
|
|
103
|
+
class LocalStorage<T> implements StorageInterface<T> {
|
|
104
|
+
get(key: string): T | null {
|
|
105
|
+
const value = localStorage.getItem(key);
|
|
106
|
+
return value ? JSON.parse(value) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
set(key: string, value: T): void {
|
|
110
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
remove(key: string): void {
|
|
114
|
+
localStorage.removeItem(key);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
clear(): void {
|
|
118
|
+
localStorage.clear();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 依赖注入
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// 服务接口
|
|
127
|
+
interface UserServiceInterface {
|
|
128
|
+
getCurrentUser(): User | null;
|
|
129
|
+
updateProfile(data: UserProfile): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 服务实现
|
|
133
|
+
@injectable()
|
|
134
|
+
class UserService implements UserServiceInterface {
|
|
135
|
+
constructor(
|
|
136
|
+
@inject('StorageService') private storage: StorageInterface<User>,
|
|
137
|
+
@inject('ApiService') private api: ApiInterface
|
|
138
|
+
) {}
|
|
139
|
+
|
|
140
|
+
getCurrentUser(): User | null {
|
|
141
|
+
return this.storage.get('currentUser');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async updateProfile(data: UserProfile): Promise<void> {
|
|
145
|
+
await this.api.put('/user/profile', data);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3. 面向配置编程
|
|
151
|
+
|
|
152
|
+
项目采用配置驱动的方式来管理功能特性:
|
|
153
|
+
|
|
154
|
+
#### 配置定义
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// 配置接口
|
|
158
|
+
interface AppConfig {
|
|
159
|
+
api: {
|
|
160
|
+
baseUrl: string;
|
|
161
|
+
timeout: number;
|
|
162
|
+
retries: number;
|
|
163
|
+
};
|
|
164
|
+
auth: {
|
|
165
|
+
tokenKey: string;
|
|
166
|
+
tokenPrefix: string;
|
|
167
|
+
expiresIn: number;
|
|
168
|
+
};
|
|
169
|
+
theme: {
|
|
170
|
+
defaultTheme: 'light' | 'dark';
|
|
171
|
+
enableUserTheme: boolean;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 配置实现
|
|
176
|
+
const appConfig: AppConfig = {
|
|
177
|
+
api: {
|
|
178
|
+
baseUrl: process.env.API_BASE_URL || '/api',
|
|
179
|
+
timeout: 5000,
|
|
180
|
+
retries: 3
|
|
181
|
+
},
|
|
182
|
+
auth: {
|
|
183
|
+
tokenKey: 'auth_token',
|
|
184
|
+
tokenPrefix: 'Bearer',
|
|
185
|
+
expiresIn: 7200
|
|
186
|
+
},
|
|
187
|
+
theme: {
|
|
188
|
+
defaultTheme: 'light',
|
|
189
|
+
enableUserTheme: true
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### 配置驱动
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// 配置驱动的功能实现
|
|
198
|
+
@injectable()
|
|
199
|
+
class ApiService {
|
|
200
|
+
constructor(
|
|
201
|
+
@inject('AppConfig') private config: AppConfig,
|
|
202
|
+
@inject('HttpClient') private http: HttpClient
|
|
203
|
+
) {
|
|
204
|
+
// 使用配置初始化服务
|
|
205
|
+
this.http.setBaseUrl(config.api.baseUrl);
|
|
206
|
+
this.http.setTimeout(config.api.timeout);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async request<T>(options: RequestOptions): Promise<T> {
|
|
210
|
+
let retries = this.config.api.retries;
|
|
211
|
+
|
|
212
|
+
while (retries > 0) {
|
|
213
|
+
try {
|
|
214
|
+
return await this.http.request(options);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
retries--;
|
|
217
|
+
if (retries === 0) throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 类型系统使用
|
|
225
|
+
|
|
226
|
+
### 1. 泛型
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// 泛型接口
|
|
230
|
+
interface Repository<T> {
|
|
231
|
+
findById(id: string): Promise<T>;
|
|
232
|
+
findAll(): Promise<T[]>;
|
|
233
|
+
create(data: Omit<T, 'id'>): Promise<T>;
|
|
234
|
+
update(id: string, data: Partial<T>): Promise<T>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 泛型类
|
|
238
|
+
class ApiRepository<T> implements Repository<T> {
|
|
239
|
+
constructor(private endpoint: string) {}
|
|
240
|
+
|
|
241
|
+
async findById(id: string): Promise<T> {
|
|
242
|
+
return api.get(`${this.endpoint}/${id}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ... 其他方法实现
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 2. 类型工具
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// 类型映射
|
|
253
|
+
type Nullable<T> = { [P in keyof T]: T[P] | null };
|
|
254
|
+
|
|
255
|
+
// 条件类型
|
|
256
|
+
type ArrayType<T> = T extends Array<infer U> ? U : never;
|
|
257
|
+
|
|
258
|
+
// 工具类型
|
|
259
|
+
type PartialDeep<T> = {
|
|
260
|
+
[P in keyof T]?: T[P] extends object ? PartialDeep<T[P]> : T[P];
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 3. 装饰器
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// 方法装饰器
|
|
268
|
+
function Cached(ttl: number = 3600) {
|
|
269
|
+
return function (
|
|
270
|
+
target: any,
|
|
271
|
+
propertyKey: string,
|
|
272
|
+
descriptor: PropertyDescriptor
|
|
273
|
+
) {
|
|
274
|
+
const original = descriptor.value;
|
|
275
|
+
const cache = new Map();
|
|
276
|
+
|
|
277
|
+
descriptor.value = async function (...args: any[]) {
|
|
278
|
+
const key = JSON.stringify(args);
|
|
279
|
+
if (cache.has(key)) {
|
|
280
|
+
const { value, timestamp } = cache.get(key);
|
|
281
|
+
if (Date.now() - timestamp < ttl * 1000) {
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const result = await original.apply(this, args);
|
|
287
|
+
cache.set(key, { value: result, timestamp: Date.now() });
|
|
288
|
+
return result;
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 使用装饰器
|
|
294
|
+
class UserService {
|
|
295
|
+
@Cached(1800)
|
|
296
|
+
async getUserProfile(id: string): Promise<UserProfile> {
|
|
297
|
+
return this.api.get(`/users/${id}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 最佳实践
|
|
303
|
+
|
|
304
|
+
### 1. 类型定义
|
|
305
|
+
|
|
306
|
+
- 使用 interface 定义对象结构
|
|
307
|
+
- 使用 type 定义联合类型和工具类型
|
|
308
|
+
- 优先使用 readonly 确保不可变性
|
|
309
|
+
- 合理使用可选属性
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// 好的实践
|
|
313
|
+
interface UserProfile {
|
|
314
|
+
readonly id: string;
|
|
315
|
+
name: string;
|
|
316
|
+
email: string;
|
|
317
|
+
avatar?: string;
|
|
318
|
+
preferences: Readonly<UserPreferences>;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 避免的实践
|
|
322
|
+
interface BadUserProfile {
|
|
323
|
+
[key: string]: any; // 避免使用索引签名
|
|
324
|
+
data: any; // 避免使用 any
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 2. 错误处理
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// 自定义错误类
|
|
332
|
+
class ApiError extends Error {
|
|
333
|
+
constructor(
|
|
334
|
+
public code: string,
|
|
335
|
+
message: string,
|
|
336
|
+
public data?: any
|
|
337
|
+
) {
|
|
338
|
+
super(message);
|
|
339
|
+
this.name = 'ApiError';
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 类型守卫
|
|
344
|
+
function isApiError(error: unknown): error is ApiError {
|
|
345
|
+
return error instanceof ApiError;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 错误处理
|
|
349
|
+
try {
|
|
350
|
+
await api.request();
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (isApiError(error)) {
|
|
353
|
+
handleApiError(error);
|
|
354
|
+
} else {
|
|
355
|
+
handleUnknownError(error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 3. 异步处理
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// 异步结果类型
|
|
364
|
+
interface AsyncResult<T, E = Error> {
|
|
365
|
+
data: T | null;
|
|
366
|
+
error: E | null;
|
|
367
|
+
loading: boolean;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 异步操作包装器
|
|
371
|
+
async function asyncWrapper<T, E = Error>(
|
|
372
|
+
promise: Promise<T>
|
|
373
|
+
): Promise<AsyncResult<T, E>> {
|
|
374
|
+
try {
|
|
375
|
+
const data = await promise;
|
|
376
|
+
return { data, error: null, loading: false };
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return { data: null, error: error as E, loading: false };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## 开发工具配置
|
|
384
|
+
|
|
385
|
+
### 1. TSConfig 配置
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"compilerOptions": {
|
|
390
|
+
"target": "ES2020",
|
|
391
|
+
"module": "ESNext",
|
|
392
|
+
"strict": true,
|
|
393
|
+
"esModuleInterop": true,
|
|
394
|
+
"skipLibCheck": true,
|
|
395
|
+
"forceConsistentCasingInFileNames": true,
|
|
396
|
+
"experimentalDecorators": true,
|
|
397
|
+
"emitDecoratorMetadata": true
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 2. ESLint 配置
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
407
|
+
"rules": {
|
|
408
|
+
"@typescript-eslint/explicit-function-return-type": "error",
|
|
409
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
410
|
+
"@typescript-eslint/no-unused-vars": "error"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## 调试技巧
|
|
416
|
+
|
|
417
|
+
### 1. 类型断言
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// 类型断言
|
|
421
|
+
function processValue(value: unknown) {
|
|
422
|
+
if (typeof value === 'string') {
|
|
423
|
+
return value.toUpperCase();
|
|
424
|
+
}
|
|
425
|
+
return String(value);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 类型收窄
|
|
429
|
+
function isString(value: unknown): value is string {
|
|
430
|
+
return typeof value === 'string';
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 2. 调试工具
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// 类型检查
|
|
438
|
+
type Debug<T> = {
|
|
439
|
+
[P in keyof T]: T[P];
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// 运行时类型检查
|
|
443
|
+
function assertType<T>(value: unknown, message?: string): asserts value is T {
|
|
444
|
+
if (!isValidType<T>(value)) {
|
|
445
|
+
throw new TypeError(message || 'Type assertion failed');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## 性能优化
|
|
451
|
+
|
|
452
|
+
### 1. 类型优化
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// 使用类型缓存
|
|
456
|
+
type CachedType<T> = T extends Function ? T : Readonly<T>;
|
|
457
|
+
|
|
458
|
+
// 避免过度使用泛型
|
|
459
|
+
type SimpleType = string | number;
|
|
460
|
+
type ComplexType<T> = T extends SimpleType ? T : never;
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 2. 编译优化
|
|
464
|
+
|
|
465
|
+
- 使用 project references
|
|
466
|
+
- 启用增量编译
|
|
467
|
+
- 优化类型导入
|
|
468
|
+
|
|
469
|
+
## 参考资源
|
|
470
|
+
|
|
471
|
+
- [TypeScript 官方文档](https://www.typescriptlang.org/docs/)
|
|
472
|
+
- [TypeScript 设计模式](https://refactoring.guru/design-patterns/typescript)
|
|
473
|
+
- [Clean Code TypeScript](https://github.com/labs42io/clean-code-typescript)
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"dev": "vite --mode localhost",
|
|
47
47
|
"dev:staging": "vite --mode staging",
|
|
48
48
|
"dev:prod": "vite --mode production",
|
|
49
|
-
"build": "vite build",
|
|
49
|
+
"build": "npm run lint && vite build",
|
|
50
50
|
"lint": "tsc -b --noEmit && eslint ./src --fix",
|
|
51
|
-
"prettier": "prettier --write ./src",
|
|
51
|
+
"prettier": "prettier --write ./src ./docs ./config",
|
|
52
52
|
"preview": "vite preview",
|
|
53
53
|
"test": "vitest run"
|
|
54
54
|
},
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
RequestCommonPlugin
|
|
9
9
|
} from '@qlover/corekit-bridge';
|
|
10
10
|
import { RequestLogger } from '../cases/RequestLogger';
|
|
11
|
-
import { IOCIdentifier } from '
|
|
12
|
-
import {
|
|
11
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
12
|
+
import type { AppConfig } from '../cases/AppConfig';
|
|
13
13
|
|
|
14
14
|
const apiApiAdapter = new RequestAdapterFetch({
|
|
15
|
-
|
|
15
|
+
responseType: 'json'
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
// 使用 RequestScheduler
|
|
@@ -24,6 +24,11 @@ const apiApi = new RequestScheduler(apiApiAdapter);
|
|
|
24
24
|
export const AiApiBootstarp: BootstrapExecutorPlugin = {
|
|
25
25
|
pluginName: 'AiApiBootstarp',
|
|
26
26
|
onBefore({ parameters: { ioc } }) {
|
|
27
|
+
const appConfig = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
|
|
28
|
+
|
|
29
|
+
// dynamic set baseURL
|
|
30
|
+
apiApiAdapter.config.baseURL = appConfig.aiApiBaseUrl;
|
|
31
|
+
|
|
27
32
|
apiApiAdapter.usePlugin(new FetchURLPlugin());
|
|
28
33
|
apiApiAdapter.usePlugin(
|
|
29
34
|
new RequestCommonPlugin({
|
|
@@ -40,13 +45,13 @@ export function aiHello(data: {
|
|
|
40
45
|
messages: { role: string; content: string }[];
|
|
41
46
|
}) {
|
|
42
47
|
return apiApi.request<
|
|
43
|
-
typeof data,
|
|
44
48
|
{
|
|
45
49
|
id: string;
|
|
46
50
|
object: string;
|
|
47
51
|
created: number;
|
|
48
52
|
choices: { message: { role: string; content: string } }[];
|
|
49
|
-
}
|
|
53
|
+
},
|
|
54
|
+
typeof data
|
|
50
55
|
>({
|
|
51
56
|
url: '/chat/completions',
|
|
52
57
|
method: 'POST',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AppConfig } from '@/base/cases/AppConfig';
|
|
2
|
-
import { IOCIdentifier } from '
|
|
2
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
3
3
|
import { RequestAdapterFetch } from '@qlover/fe-corekit';
|
|
4
4
|
import { inject, injectable } from 'inversify';
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
ApiPickDataPlugin
|
|
12
12
|
} from '@qlover/corekit-bridge';
|
|
13
13
|
import { FeApi } from './FeApi';
|
|
14
|
-
import { IOCIdentifier } from '
|
|
14
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
15
15
|
import { RequestLogger } from '@/base/cases/RequestLogger';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -7,26 +7,21 @@ import {
|
|
|
7
7
|
GetIpInfoTransaction,
|
|
8
8
|
UserApiGetUserInfoTransaction,
|
|
9
9
|
UserApiLoginTransaction,
|
|
10
|
-
UserApiTestApiCatchResultTransaction
|
|
10
|
+
UserApiTestApiCatchResultTransaction,
|
|
11
|
+
UserInfo
|
|
11
12
|
} from './UserApiType';
|
|
12
13
|
import { inject, injectable } from 'inversify';
|
|
13
14
|
import { UserApiAdapter } from './UserApiAdapter';
|
|
14
15
|
import { UserApiConfig } from './UserApiBootstarp';
|
|
15
|
-
import {
|
|
16
|
+
import type {
|
|
16
17
|
LoginResponseData,
|
|
17
18
|
UserAuthApiInterface,
|
|
18
|
-
UserAuthStoreInterface
|
|
19
|
-
UserAuthState
|
|
19
|
+
UserAuthStoreInterface
|
|
20
20
|
} from '@qlover/corekit-bridge';
|
|
21
|
-
import {
|
|
22
|
-
RegisterFormData,
|
|
23
|
-
UserServiceUserInfo
|
|
24
|
-
} from '@/base/services/UserService';
|
|
21
|
+
import { RegisterFormData } from '@/base/services/UserService';
|
|
25
22
|
import { RES_NO_TOKEN } from '@config/Identifier';
|
|
26
23
|
import { AppError } from '@/base/cases/AppError';
|
|
27
24
|
|
|
28
|
-
export type UserApiState = UserAuthState<UserServiceUserInfo>;
|
|
29
|
-
|
|
30
25
|
/**
|
|
31
26
|
* UserApi
|
|
32
27
|
*
|
|
@@ -37,9 +32,9 @@ export type UserApiState = UserAuthState<UserServiceUserInfo>;
|
|
|
37
32
|
@injectable()
|
|
38
33
|
export class UserApi
|
|
39
34
|
extends RequestTransaction<UserApiConfig>
|
|
40
|
-
implements UserAuthApiInterface<
|
|
35
|
+
implements UserAuthApiInterface<UserInfo>
|
|
41
36
|
{
|
|
42
|
-
protected store: UserAuthStoreInterface<
|
|
37
|
+
protected store: UserAuthStoreInterface<UserInfo> | null = null;
|
|
43
38
|
|
|
44
39
|
constructor(
|
|
45
40
|
@inject(FetchAbortPlugin) private abortPlugin: FetchAbortPlugin,
|
|
@@ -48,7 +43,7 @@ export class UserApi
|
|
|
48
43
|
super(adapter);
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
getStore(): UserAuthStoreInterface<
|
|
46
|
+
getStore(): UserAuthStoreInterface<UserInfo> | null {
|
|
52
47
|
return this.store;
|
|
53
48
|
}
|
|
54
49
|
|
|
@@ -56,7 +51,7 @@ export class UserApi
|
|
|
56
51
|
* @override
|
|
57
52
|
* @param store
|
|
58
53
|
*/
|
|
59
|
-
setStore(store: UserAuthStoreInterface<
|
|
54
|
+
setStore(store: UserAuthStoreInterface<UserInfo>): void {
|
|
60
55
|
this.store = store;
|
|
61
56
|
}
|
|
62
57
|
|
|
@@ -130,9 +125,7 @@ export class UserApi
|
|
|
130
125
|
* @override
|
|
131
126
|
* @returns
|
|
132
127
|
*/
|
|
133
|
-
async getUserInfo(): Promise<
|
|
134
|
-
UserApiGetUserInfoTransaction['response']['data']
|
|
135
|
-
> {
|
|
128
|
+
async getUserInfo(): Promise<UserInfo> {
|
|
136
129
|
const response =
|
|
137
130
|
await this.get<UserApiGetUserInfoTransaction>('/api/userinfo');
|
|
138
131
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AppConfig } from '@/base/cases/AppConfig';
|
|
2
|
-
import { IOCIdentifier } from '
|
|
2
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
3
3
|
import { RequestAdapterFetch } from '@qlover/fe-corekit';
|
|
4
4
|
import { inject, injectable } from 'inversify';
|
|
5
5
|
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
RequestAdapterResponse,
|
|
5
5
|
RequestTransactionInterface
|
|
6
6
|
} from '@qlover/fe-corekit';
|
|
7
|
-
import { IOC
|
|
7
|
+
import { IOC } from '@/core/IOC';
|
|
8
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
8
9
|
import { RequestLogger } from '@/base/cases/RequestLogger';
|
|
9
10
|
import { FetchURLPlugin } from '@qlover/fe-corekit';
|
|
10
11
|
import {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { UserApiTransaction } from './UserApiBootstarp';
|
|
2
2
|
|
|
3
|
+
export type UserInfo = {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
picture: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
3
9
|
export type GetIpInfoTransaction = UserApiTransaction<
|
|
4
10
|
undefined,
|
|
5
11
|
{
|
|
@@ -35,11 +41,7 @@ export type UserApiGetRandomUser = UserApiTransaction<
|
|
|
35
41
|
|
|
36
42
|
export type UserApiGetUserInfoTransaction = UserApiTransaction<
|
|
37
43
|
string,
|
|
38
|
-
|
|
39
|
-
name: string;
|
|
40
|
-
email: string;
|
|
41
|
-
picture: string;
|
|
42
|
-
}
|
|
44
|
+
UserInfo
|
|
43
45
|
>;
|
|
44
46
|
|
|
45
47
|
export type UserApiLoginTransaction = UserApiTransaction<
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { ExecutorContext, ExecutorPlugin } from '@qlover/fe-corekit';
|
|
2
2
|
import type { LoggerInterface } from '@qlover/logger';
|
|
3
|
-
import { inject } from 'inversify';
|
|
3
|
+
import { inject, injectable } from 'inversify';
|
|
4
4
|
import { I18nService } from '../services/I18nService';
|
|
5
|
-
import { IOCIdentifier } from '
|
|
5
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* When throw error, it will be converted to i18n key
|
|
9
9
|
*
|
|
10
10
|
* If the error thrown is an i18n key, it will be converted to the corresponding text
|
|
11
11
|
*/
|
|
12
|
+
@injectable()
|
|
12
13
|
export class I18nKeyErrorPlugin implements ExecutorPlugin {
|
|
13
14
|
readonly pluginName = 'I18nKeyErrorPlugin';
|
|
14
15
|
|