@qlover/create-app 0.6.2 → 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 +35 -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/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 +7 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
- package/dist/templates/react-app/docs/zh/env.md +24 -25
- package/dist/templates/react-app/docs/zh/global.md +28 -27
- 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 +44 -32
- 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 +1 -1
- 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/package.json +1 -1
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# IOC Container
|
|
2
|
+
|
|
3
|
+
## What is IOC?
|
|
4
|
+
|
|
5
|
+
IOC (Inversion of Control) is a design pattern that hands over the creation of objects and management of their dependencies to a container, rather than creating objects directly in the code.
|
|
6
|
+
|
|
7
|
+
**In simple terms**: Like a factory producing products, you don't need to know how products are manufactured; you just tell the factory what product you need, and the factory will provide it.
|
|
8
|
+
|
|
9
|
+
## IOC Implementation in Project
|
|
10
|
+
|
|
11
|
+
### Core Technology Stack
|
|
12
|
+
|
|
13
|
+
- **InversifyJS**: As the IOC container implementation (you can also manually implement your own container)
|
|
14
|
+
- **TypeScript**: Provides type safety
|
|
15
|
+
- **Decorator Pattern**: Uses `@injectable()` and `@inject()` decorators
|
|
16
|
+
|
|
17
|
+
### Core File Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
config/
|
|
21
|
+
├── IOCIdentifier.ts # IOC identifier definitions
|
|
22
|
+
src/
|
|
23
|
+
├── core/
|
|
24
|
+
│ ├── IOC.ts # IOC main entry
|
|
25
|
+
│ ├── registers/ # Registers directory
|
|
26
|
+
│ │ ├── RegisterGlobals.ts # Global service registration
|
|
27
|
+
│ │ ├── RegisterCommon.ts # Common service registration
|
|
28
|
+
│ │ └── RegisterControllers.ts # Controller registration
|
|
29
|
+
│ └── globals.ts # Global instances
|
|
30
|
+
├── base/
|
|
31
|
+
│ └── cases/
|
|
32
|
+
│ └── InversifyContainer.ts # Inversify container implementation
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Basic Concepts
|
|
36
|
+
|
|
37
|
+
### 1. IOC Identifiers
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// config/IOCIdentifier.ts
|
|
41
|
+
export const IOCIdentifier = Object.freeze({
|
|
42
|
+
JSON: 'JSON',
|
|
43
|
+
LocalStorage: 'LocalStorage',
|
|
44
|
+
Logger: 'Logger',
|
|
45
|
+
AppConfig: 'AppConfig'
|
|
46
|
+
// ... more identifiers
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. IOC Identifier Mapping
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// core/IOC.ts
|
|
54
|
+
export interface IOCIdentifierMap {
|
|
55
|
+
[IOCIdentifier.JSON]: import('@qlover/fe-corekit').JSONSerializer;
|
|
56
|
+
[IOCIdentifier.LocalStorage]: import('@qlover/fe-corekit').ObjectStorage<
|
|
57
|
+
string,
|
|
58
|
+
string
|
|
59
|
+
>;
|
|
60
|
+
[IOCIdentifier.Logger]: import('@qlover/logger').LoggerInterface;
|
|
61
|
+
[IOCIdentifier.AppConfig]: import('@qlover/corekit-bridge').EnvConfigInterface;
|
|
62
|
+
// ... more mappings
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. IOC Function
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// core/IOC.ts
|
|
70
|
+
export const IOC = createIOCFunction<IOCIdentifierMap>(
|
|
71
|
+
new InversifyContainer()
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
### 1. Getting Service Instances
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// Using class name
|
|
81
|
+
const userService = IOC(UserService);
|
|
82
|
+
|
|
83
|
+
// Using string identifier
|
|
84
|
+
const logger = IOC('Logger');
|
|
85
|
+
// Or
|
|
86
|
+
const logger = IOC(IOCIdentifier.Logger);
|
|
87
|
+
|
|
88
|
+
// Using AppConfig
|
|
89
|
+
const appConfig = IOC(IOCIdentifier.AppConfig);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Using Dependency Injection in Services
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { inject, injectable } from 'inversify';
|
|
96
|
+
import { UserApi } from '@/base/apis/userApi/UserApi';
|
|
97
|
+
import { RouteService } from './RouteService';
|
|
98
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
99
|
+
|
|
100
|
+
@injectable()
|
|
101
|
+
export class UserService extends UserAuthService<UserInfo> {
|
|
102
|
+
constructor(
|
|
103
|
+
@inject(RouteService) protected routerService: RouteService,
|
|
104
|
+
@inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
|
|
105
|
+
@inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
|
|
106
|
+
@inject(IOCIdentifier.LocalStorageEncrypt)
|
|
107
|
+
storage: SyncStorageInterface<string, string>
|
|
108
|
+
) {
|
|
109
|
+
super(userApi, {
|
|
110
|
+
userStorage: {
|
|
111
|
+
key: appConfig.userInfoStorageKey,
|
|
112
|
+
storage: storage
|
|
113
|
+
},
|
|
114
|
+
credentialStorage: {
|
|
115
|
+
key: appConfig.userTokenStorageKey,
|
|
116
|
+
storage: storage
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Using in Bootstrap
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
// Register services in bootstrapper
|
|
127
|
+
bootstrap.use([
|
|
128
|
+
IOC(UserService), // User service
|
|
129
|
+
IOC(I18nService), // Internationalization service
|
|
130
|
+
new UserApiBootstarp() // API configuration
|
|
131
|
+
]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Service Registration
|
|
135
|
+
|
|
136
|
+
### 1. Global Service Registration
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// core/registers/RegisterGlobals.ts
|
|
140
|
+
export const RegisterGlobals: IOCRegister = {
|
|
141
|
+
register(container, _, options): void {
|
|
142
|
+
// Register application configuration
|
|
143
|
+
container.bind(IOCIdentifier.AppConfig, options!.appConfig);
|
|
144
|
+
|
|
145
|
+
// Register logging service
|
|
146
|
+
container.bind(Logger, logger);
|
|
147
|
+
container.bind(IOCIdentifier.Logger, logger);
|
|
148
|
+
|
|
149
|
+
// Register storage services
|
|
150
|
+
container.bind(IOCIdentifier.LocalStorage, localStorage);
|
|
151
|
+
container.bind(IOCIdentifier.LocalStorageEncrypt, localStorageEncrypt);
|
|
152
|
+
container.bind(IOCIdentifier.CookieStorage, cookieStorage);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2. Common Service Registration
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// core/registers/RegisterCommon.ts
|
|
161
|
+
export const RegisterCommon: IOCRegister = {
|
|
162
|
+
register(container, _, options): void {
|
|
163
|
+
const AppConfig = container.get(IOCIdentifier.AppConfig);
|
|
164
|
+
|
|
165
|
+
// Register API-related services
|
|
166
|
+
const feApiToken = new TokenStorage(AppConfig.userTokenStorageKey, {
|
|
167
|
+
storage: container.get(IOCIdentifier.LocalStorageEncrypt)
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
container.bind(IOCIdentifier.FeApiToken, feApiToken);
|
|
171
|
+
container.bind(IOCIdentifier.FeApiCommonPlugin, feApiRequestCommonPlugin);
|
|
172
|
+
|
|
173
|
+
// Register theme service
|
|
174
|
+
container.bind(
|
|
175
|
+
ThemeService,
|
|
176
|
+
new ThemeService({
|
|
177
|
+
...themeConfig,
|
|
178
|
+
storage: localStorage
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Register router service
|
|
183
|
+
container.bind(
|
|
184
|
+
RouteService,
|
|
185
|
+
new RouteService({
|
|
186
|
+
routes: baseRoutes,
|
|
187
|
+
logger
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Register i18n service
|
|
192
|
+
container.bind(I18nService, new I18nService(options!.pathname));
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 3. Controller Registration
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
// core/registers/RegisterControllers.ts
|
|
201
|
+
export class RegisterControllers implements IOCRegister {
|
|
202
|
+
register(
|
|
203
|
+
container: IOCContainer,
|
|
204
|
+
_: IOCManagerInterface<IOCContainer>
|
|
205
|
+
): void {
|
|
206
|
+
// Register controllers
|
|
207
|
+
const jsonStorageController = new JSONStorageController(localStorage);
|
|
208
|
+
container.bind(JSONStorageController, jsonStorageController);
|
|
209
|
+
|
|
210
|
+
// Configure processor
|
|
211
|
+
container
|
|
212
|
+
.get(ProcesserExecutor)
|
|
213
|
+
.use(container.get(I18nKeyErrorPlugin))
|
|
214
|
+
.use(container.get(UserService));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Practical Application Scenarios
|
|
220
|
+
|
|
221
|
+
### 1. User Authentication Service
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
@injectable()
|
|
225
|
+
export class UserService extends UserAuthService<UserInfo> {
|
|
226
|
+
constructor(
|
|
227
|
+
@inject(RouteService) protected routerService: RouteService,
|
|
228
|
+
@inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
|
|
229
|
+
@inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
|
|
230
|
+
@inject(IOCIdentifier.LocalStorageEncrypt)
|
|
231
|
+
storage: SyncStorageInterface<string, string>
|
|
232
|
+
) {
|
|
233
|
+
super(userApi, {
|
|
234
|
+
userStorage: {
|
|
235
|
+
key: appConfig.userInfoStorageKey,
|
|
236
|
+
storage: storage
|
|
237
|
+
},
|
|
238
|
+
credentialStorage: {
|
|
239
|
+
key: appConfig.userTokenStorageKey,
|
|
240
|
+
storage: storage
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async onBefore(): Promise<void> {
|
|
246
|
+
if (this.isAuthenticated()) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const userToken = this.getToken();
|
|
251
|
+
if (!userToken) {
|
|
252
|
+
throw new AppError('NO_USER_TOKEN');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await this.userInfo();
|
|
256
|
+
this.store.authSuccess();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 2. API Configuration Service
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
export class UserApiBootstarp implements BootstrapExecutorPlugin {
|
|
265
|
+
readonly pluginName = 'UserApiBootstarp';
|
|
266
|
+
|
|
267
|
+
onBefore({ parameters: { ioc } }: BootstrapContext): void {
|
|
268
|
+
// Get UserApi instance through IOC and configure plugins
|
|
269
|
+
ioc
|
|
270
|
+
.get<UserApi>(UserApi)
|
|
271
|
+
.usePlugin(new FetchURLPlugin())
|
|
272
|
+
.usePlugin(IOC.get(IOCIdentifier.ApiMockPlugin))
|
|
273
|
+
.usePlugin(IOC.get(RequestLogger));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 3. Using in Components
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// Using IOC services in React components
|
|
282
|
+
function UserProfile() {
|
|
283
|
+
const userService = IOC(UserService);
|
|
284
|
+
const { user } = useStore(userService.store);
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div>
|
|
288
|
+
<h1>Welcome, {user?.name}</h1>
|
|
289
|
+
<button onClick={() => userService.logout()}>Logout</button>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Best Practices
|
|
296
|
+
|
|
297
|
+
### 1. Service Design Principles
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
// ✅ Good design: Single responsibility
|
|
301
|
+
@injectable()
|
|
302
|
+
export class UserService {
|
|
303
|
+
constructor(
|
|
304
|
+
@inject(UserApi) private userApi: UserApi,
|
|
305
|
+
@inject(IOCIdentifier.AppConfig) private appConfig: AppConfig
|
|
306
|
+
) {}
|
|
307
|
+
|
|
308
|
+
async getUserInfo(): Promise<UserInfo> {
|
|
309
|
+
return this.userApi.getUserInfo();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ❌ Bad design: Too many responsibilities
|
|
314
|
+
@injectable()
|
|
315
|
+
export class BadService {
|
|
316
|
+
constructor(
|
|
317
|
+
@inject(UserApi) private userApi: UserApi,
|
|
318
|
+
@inject(RouteService) private routeService: RouteService,
|
|
319
|
+
@inject(ThemeService) private themeService: ThemeService,
|
|
320
|
+
@inject(I18nService) private i18nService: I18nService
|
|
321
|
+
) {}
|
|
322
|
+
|
|
323
|
+
// One service doing too many things
|
|
324
|
+
async handleUserAction(): Promise<void> {
|
|
325
|
+
// Handle user logic
|
|
326
|
+
// Handle routing logic
|
|
327
|
+
// Handle theme logic
|
|
328
|
+
// Handle i18n logic
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 2. Dependency Injection Best Practices
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
// ✅ Use interfaces instead of concrete implementations
|
|
337
|
+
@injectable()
|
|
338
|
+
export class UserService {
|
|
339
|
+
constructor(
|
|
340
|
+
@inject('UserApiInterface') private userApi: UserAuthApiInterface<UserInfo>
|
|
341
|
+
) {}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ✅ Use identifiers instead of class names
|
|
345
|
+
@injectable()
|
|
346
|
+
export class SomeService {
|
|
347
|
+
constructor(
|
|
348
|
+
@inject(IOCIdentifier.Logger) private logger: LoggerInterface,
|
|
349
|
+
@inject(IOCIdentifier.AppConfig) private appConfig: AppConfig
|
|
350
|
+
) {}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### 3. Error Handling
|
|
355
|
+
|
|
356
|
+
```tsx
|
|
357
|
+
@injectable()
|
|
358
|
+
export class SafeService {
|
|
359
|
+
constructor(@inject(IOCIdentifier.Logger) private logger: LoggerInterface) {}
|
|
360
|
+
|
|
361
|
+
async doSomething(): Promise<void> {
|
|
362
|
+
try {
|
|
363
|
+
// Business logic
|
|
364
|
+
} catch (error) {
|
|
365
|
+
this.logger.error('Operation failed:', error);
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Debugging and Testing
|
|
373
|
+
|
|
374
|
+
### 1. Debugging IOC Container
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// Check if service is registered
|
|
378
|
+
const container = IOC.implemention;
|
|
379
|
+
const isRegistered = container.isBound(UserService);
|
|
380
|
+
|
|
381
|
+
// Get all registered services
|
|
382
|
+
const bindings = container.getAll(UserService);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 2. Unit Testing
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { Container } from 'inversify';
|
|
389
|
+
|
|
390
|
+
describe('UserService', () => {
|
|
391
|
+
let container: Container;
|
|
392
|
+
let userService: UserService;
|
|
393
|
+
|
|
394
|
+
beforeEach(() => {
|
|
395
|
+
container = new Container();
|
|
396
|
+
|
|
397
|
+
// Register test dependencies
|
|
398
|
+
container.bind('UserApiInterface').toConstantValue(mockUserApi);
|
|
399
|
+
container.bind(IOCIdentifier.AppConfig).toConstantValue(mockAppConfig);
|
|
400
|
+
container
|
|
401
|
+
.bind(IOCIdentifier.LocalStorageEncrypt)
|
|
402
|
+
.toConstantValue(mockStorage);
|
|
403
|
+
|
|
404
|
+
userService = container.get(UserService);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should authenticate user successfully', async () => {
|
|
408
|
+
const result = await userService.onBefore();
|
|
409
|
+
expect(result).toBeDefined();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Summary
|
|
415
|
+
|
|
416
|
+
The role of IOC container in the project:
|
|
417
|
+
|
|
418
|
+
1. **Dependency Management**: Unified management of all service dependencies
|
|
419
|
+
2. **Type Safety**: Compile-time type checking through TypeScript
|
|
420
|
+
3. **Testability**: Easy to unit test and mock
|
|
421
|
+
4. **Maintainability**: Clear dependency relationships, easy to understand and modify
|
|
422
|
+
5. **Extensibility**: Easy to add new services and dependencies
|
|
423
|
+
|
|
424
|
+
Through proper use of the IOC container, code becomes more modular, testable, and maintainable.
|