@linyjs/plugin-docs 0.0.15 → 0.0.17
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 +4 -0
- package/docs/api-reference/index.md +3 -0
- package/docs/faq.md +4 -1
- package/docs/guides/authentication.md +313 -240
- package/docs/guides/client-hooks.md +626 -0
- package/docs/guides/client-module.md +5 -2
- package/docs/guides/client-routes.md +631 -0
- package/docs/guides/dependency-injection.md +3 -0
- package/docs/guides/permissions.md +124 -53
- package/docs/guides/server-module.md +2 -0
- package/docs/guides/shared-module-interface.md +117 -51
- package/docs/guides/state-management.md +905 -0
- package/docs/guides/ui-slots.md +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# 公共 Hooks 使用
|
|
2
|
+
|
|
3
|
+
本指南详细介绍 linyjs 框架提供的公共 Hooks,用于在组件中消费跨模块注册的资源(组件、Hooks、服务)和路由功能。
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
linyjs 通过 React Context 提供全局注册表,使模块间可以共享资源。框架提供以下 5 个公共 Hooks:
|
|
8
|
+
|
|
9
|
+
| Hook | 用途 | 返回值 |
|
|
10
|
+
|------|------|--------|
|
|
11
|
+
| `useClientComponent` | 获取跨模块注册的组件 | `IComponent \| undefined` |
|
|
12
|
+
| `useClientHook` | 获取跨模块注册的 Hook | `Function \| undefined` |
|
|
13
|
+
| `useClientService` | 获取跨模块注册的服务实例 | `T \| undefined` |
|
|
14
|
+
| `useClientRoute` | 获取路由功能(导航、参数等) | `any` |
|
|
15
|
+
|
|
16
|
+
所有 Hooks 从 `@linyjs/client-module-interface` 导入:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import {
|
|
20
|
+
useClientComponent,
|
|
21
|
+
useClientHook,
|
|
22
|
+
useClientService,
|
|
23
|
+
useClientRoute,
|
|
24
|
+
} from '@linyjs/client-module-interface';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 资源引用格式
|
|
28
|
+
|
|
29
|
+
所有跨模块资源使用统一的引用格式:
|
|
30
|
+
|
|
31
|
+
### RegistryKey 格式
|
|
32
|
+
|
|
33
|
+
资源在全局注册表中的 key 采用 `moduleName.serviceTag.name` 三段式格式:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
{moduleName}.{serviceTag}.{name}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 两种引用方式
|
|
40
|
+
|
|
41
|
+
| 引用方式 | 格式 | 示例 | 说明 |
|
|
42
|
+
|----------|------|------|------|
|
|
43
|
+
| 模块内引用 | `serviceTag.name` | `'userComponents.Avatar'` | 省略 moduleName,框架自动补全 |
|
|
44
|
+
| 跨模块引用 | `moduleName.serviceTag.name` | `'userModule.userComponents.Avatar'` | 显式指定完整路径 |
|
|
45
|
+
|
|
46
|
+
> 💡 **自动补全规则**:如果传入的引用路径只有 1 个点(即 `serviceTag.name`),框架会尝试补全当前模块名。如果传入 2 个及以上点(即 `moduleName.serviceTag.name`),框架视为完整路径直接使用。
|
|
47
|
+
|
|
48
|
+
### 不同资源类型的引用格式
|
|
49
|
+
|
|
50
|
+
| 资源类型 | 模块内引用 | 跨模块引用 |
|
|
51
|
+
|----------|-----------|-----------|
|
|
52
|
+
| 组件 | `'userComponents.Avatar'` | `'userModule.userComponents.Avatar'` |
|
|
53
|
+
| Hook | `'authHooks.useAuth'` | `'authModule.authHooks.useAuth'` |
|
|
54
|
+
| 服务 | `'userService'` | `'userModule.userService'` |
|
|
55
|
+
|
|
56
|
+
> ⚠️ **服务的引用格式不同**:服务只有两段 `moduleName.serviceTag`,不含 `.name`,因为服务本身就是整个实例。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## useClientComponent
|
|
61
|
+
|
|
62
|
+
获取跨模块注册的 React 组件。
|
|
63
|
+
|
|
64
|
+
### 签名
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
function useClientComponent<P = any>(refPath: string): IComponent<P> | undefined
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 参数
|
|
71
|
+
|
|
72
|
+
| 参数 | 类型 | 说明 |
|
|
73
|
+
|------|------|------|
|
|
74
|
+
| `refPath` | `string` | 组件引用路径 |
|
|
75
|
+
| `P`(泛型) | - | 组件 Props 类型,用于类型推断 |
|
|
76
|
+
|
|
77
|
+
### 返回值
|
|
78
|
+
|
|
79
|
+
返回对应的 React 组件。如果未找到,返回 `undefined` 并在控制台输出警告。
|
|
80
|
+
|
|
81
|
+
### 使用示例
|
|
82
|
+
|
|
83
|
+
#### 跨模块引用组件
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { useClientComponent } from '@linyjs/client-module-interface';
|
|
87
|
+
|
|
88
|
+
function MyPage() {
|
|
89
|
+
// 从 userModule 获取 Avatar 组件
|
|
90
|
+
const Avatar = useClientComponent<{ src: string; size?: number }>(
|
|
91
|
+
'userModule.userComponents.Avatar'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!Avatar) {
|
|
95
|
+
return <div>组件加载中...</div>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return <Avatar src="/avatar.jpg" size={48} />;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### 模块内引用组件
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// 假设当前组件属于 userModule
|
|
106
|
+
function UserCard() {
|
|
107
|
+
// 省略 moduleName,框架自动补全
|
|
108
|
+
const Avatar = useClientComponent('userComponents.Avatar');
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div>
|
|
112
|
+
{Avatar && <Avatar src="/avatar.jpg" />}
|
|
113
|
+
<span>用户名</span>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## useClientHook
|
|
122
|
+
|
|
123
|
+
获取跨模块注册的自定义 Hook。
|
|
124
|
+
|
|
125
|
+
### 签名
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
function useClientHook<T extends Function>(refPath: string): T | undefined
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 参数
|
|
132
|
+
|
|
133
|
+
| 参数 | 类型 | 说明 |
|
|
134
|
+
|------|------|------|
|
|
135
|
+
| `refPath` | `string` | Hook 引用路径 |
|
|
136
|
+
| `T`(泛型) | `extends Function` | Hook 的函数类型 |
|
|
137
|
+
|
|
138
|
+
### 返回值
|
|
139
|
+
|
|
140
|
+
返回对应的 Hook 函数。如果未找到,返回 `undefined` 并在控制台输出警告。
|
|
141
|
+
|
|
142
|
+
### 使用示例
|
|
143
|
+
|
|
144
|
+
#### 跨模块引用 Hook
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { useClientHook } from '@linyjs/client-module-interface';
|
|
148
|
+
|
|
149
|
+
function MyComponent() {
|
|
150
|
+
// 从 authModule 获取 useAuth Hook
|
|
151
|
+
const useAuth = useClientHook<() => { user: any; login: Function; logout: Function }>(
|
|
152
|
+
'authModule.authHooks.useAuth'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!useAuth) {
|
|
156
|
+
return <div>认证功能加载中...</div>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 调用获取到的 Hook
|
|
160
|
+
const { user, login, logout } = useAuth();
|
|
161
|
+
|
|
162
|
+
if (!user) {
|
|
163
|
+
return <button onClick={() => login()}>登录</button>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div>
|
|
168
|
+
<span>欢迎, {user.name}</span>
|
|
169
|
+
<button onClick={() => logout()}>退出</button>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### 带参数的 Hook
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// 假设 dataModule 注册了一个 useFetchData Hook
|
|
179
|
+
function Dashboard() {
|
|
180
|
+
type UseFetchData = (url: string) => {
|
|
181
|
+
data: any;
|
|
182
|
+
loading: boolean;
|
|
183
|
+
error: string | null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const useFetchData = useClientHook<UseFetchData>(
|
|
187
|
+
'dataModule.dataHooks.useFetchData'
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!useFetchData) return <div>Loading...</div>;
|
|
191
|
+
|
|
192
|
+
const { data, loading, error } = useFetchData('/api/stats');
|
|
193
|
+
|
|
194
|
+
if (loading) return <div>加载中...</div>;
|
|
195
|
+
if (error) return <div>错误: {error}</div>;
|
|
196
|
+
|
|
197
|
+
return <div>{JSON.stringify(data)}</div>;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## useClientService
|
|
204
|
+
|
|
205
|
+
获取跨模块注册的服务实例。
|
|
206
|
+
|
|
207
|
+
### 签名
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
function useClientService<T = any>(refPath: string): T | undefined
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 参数
|
|
214
|
+
|
|
215
|
+
| 参数 | 类型 | 说明 |
|
|
216
|
+
|------|------|------|
|
|
217
|
+
| `refPath` | `string` | 服务引用路径 |
|
|
218
|
+
| `T`(泛型) | - | 服务实例类型 |
|
|
219
|
+
|
|
220
|
+
### 返回值
|
|
221
|
+
|
|
222
|
+
返回对应的服务实例。如果未找到,返回 `undefined` 并在控制台输出警告。
|
|
223
|
+
|
|
224
|
+
> ⚠️ **注意**:服务的引用格式是 `moduleName.serviceTag`(两段),不是三段。服务实例是整个对象,不需要 `.name`。
|
|
225
|
+
|
|
226
|
+
### 使用示例
|
|
227
|
+
|
|
228
|
+
#### 跨模块引用服务
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { useClientService } from '@linyjs/client-module-interface';
|
|
232
|
+
|
|
233
|
+
interface IAnalyticsService {
|
|
234
|
+
track(event: string, data?: Record<string, any>): void;
|
|
235
|
+
pageView(path: string): void;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function MyPage() {
|
|
239
|
+
// 从 analyticsModule 获取分析服务
|
|
240
|
+
const analytics = useClientService<IAnalyticsService>(
|
|
241
|
+
'analyticsModule.analyticsService'
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
// 记录页面访问
|
|
246
|
+
analytics?.pageView('/my-page');
|
|
247
|
+
}, []);
|
|
248
|
+
|
|
249
|
+
const handleClick = () => {
|
|
250
|
+
analytics?.track('button_click', { button: 'cta' });
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return <button onClick={handleClick}>点击</button>;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### 模块内引用服务
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// 假设当前组件属于 userModule
|
|
261
|
+
function UserList() {
|
|
262
|
+
// 省略 moduleName,框架自动补全为 userModule.userService
|
|
263
|
+
const userService = useClientService<{ getUsers: () => Promise<any[]> }>(
|
|
264
|
+
'userService'
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const [users, setUsers] = useState([]);
|
|
268
|
+
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
userService?.getUsers().then(setUsers);
|
|
271
|
+
}, []);
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<ul>
|
|
275
|
+
{users.map(user => <li key={user.id}>{user.name}</li>)}
|
|
276
|
+
</ul>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## useClientRoute
|
|
284
|
+
|
|
285
|
+
获取路由相关的组件和 Hooks。底层通过路由适配器(`IRouterAdapter`)实现,切换适配器后消费端代码不变。
|
|
286
|
+
|
|
287
|
+
### 签名
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
function useClientRoute(key: string): any
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 参数
|
|
294
|
+
|
|
295
|
+
| 参数 | 类型 | 说明 |
|
|
296
|
+
|------|------|------|
|
|
297
|
+
| `key` | `string` | 路由功能标识 |
|
|
298
|
+
|
|
299
|
+
### 可用的 Key
|
|
300
|
+
|
|
301
|
+
| Key | 返回类型 | 说明 |
|
|
302
|
+
|-----|----------|------|
|
|
303
|
+
| `'NavLink'` | `React.ComponentType` | 导航链接组件,支持 active 状态样式 |
|
|
304
|
+
| `'Link'` | `React.ComponentType` | 普通链接组件 |
|
|
305
|
+
| `'useNavigate'` | `() => NavigateFunction` | 编程式导航 Hook |
|
|
306
|
+
| `'useParams'` | `() => Record<string, string>` | 获取路由参数 Hook |
|
|
307
|
+
| `'useLocation'` | `() => Location` | 获取当前位置 Hook |
|
|
308
|
+
| `'useSearchParams'` | `() => [URLSearchParams, Function]` | 获取/设置查询参数 Hook |
|
|
309
|
+
|
|
310
|
+
### 使用示例
|
|
311
|
+
|
|
312
|
+
#### 导航链接
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { useClientRoute } from '@linyjs/client-module-interface';
|
|
316
|
+
|
|
317
|
+
function NavBar() {
|
|
318
|
+
const NavLink = useClientRoute('NavLink');
|
|
319
|
+
const Link = useClientRoute('Link');
|
|
320
|
+
|
|
321
|
+
if (!NavLink) return null;
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<nav>
|
|
325
|
+
{/* NavLink 支持 active 状态 */}
|
|
326
|
+
<NavLink to="/" end className="nav-link">
|
|
327
|
+
首页
|
|
328
|
+
</NavLink>
|
|
329
|
+
<NavLink to="/articles" className="nav-link">
|
|
330
|
+
文章
|
|
331
|
+
</NavLink>
|
|
332
|
+
<NavLink to="/kanban" className="nav-link">
|
|
333
|
+
看板
|
|
334
|
+
</NavLink>
|
|
335
|
+
</nav>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### 编程式导航
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
function LoginForm() {
|
|
344
|
+
const useNavigate = useClientRoute('useNavigate');
|
|
345
|
+
const navigate = useNavigate();
|
|
346
|
+
|
|
347
|
+
const handleLogin = async (credentials) => {
|
|
348
|
+
const result = await login(credentials);
|
|
349
|
+
if (result.success) {
|
|
350
|
+
navigate('/dashboard'); // 导航到仪表盘
|
|
351
|
+
// navigate('/dashboard', { replace: true }); // 替换历史记录
|
|
352
|
+
} else {
|
|
353
|
+
navigate('/login?error=invalid');
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
return <button onClick={() => handleLogin({ ... })}>登录</button>;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### 获取路由参数
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
function TaskDetail() {
|
|
365
|
+
const useParams = useClientRoute('useParams');
|
|
366
|
+
const params = useParams();
|
|
367
|
+
|
|
368
|
+
return <div>任务 ID: {params.id}</div>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 多参数路由 /groups/:groupId/tasks/:taskId
|
|
372
|
+
function GroupTaskDetail() {
|
|
373
|
+
const useParams = useClientRoute('useParams');
|
|
374
|
+
const { groupId, taskId } = useParams();
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<div>
|
|
378
|
+
分组: {groupId}, 任务: {taskId}
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### 获取当前位置
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
function Breadcrumb() {
|
|
388
|
+
const useLocation = useClientRoute('useLocation');
|
|
389
|
+
const location = useLocation();
|
|
390
|
+
|
|
391
|
+
const paths = location.pathname.split('/').filter(Boolean);
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<nav>
|
|
395
|
+
{paths.map((path, index) => (
|
|
396
|
+
<span key={index}>
|
|
397
|
+
{index > 0 && ' / '}
|
|
398
|
+
{path}
|
|
399
|
+
</span>
|
|
400
|
+
))}
|
|
401
|
+
</nav>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### 获取和设置查询参数
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
function ArticleList() {
|
|
410
|
+
const useSearchParams = useClientRoute('useSearchParams');
|
|
411
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
412
|
+
|
|
413
|
+
const page = Number(searchParams.get('page') || '1');
|
|
414
|
+
const limit = Number(searchParams.get('limit') || '10');
|
|
415
|
+
|
|
416
|
+
const handlePageChange = (newPage: number) => {
|
|
417
|
+
setSearchParams({ page: String(newPage), limit: String(limit) });
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<div>
|
|
422
|
+
<p>第 {page} 页,每页 {limit} 条</p>
|
|
423
|
+
<button onClick={() => handlePageChange(page + 1)}>下一页</button>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
> 📖 **路由功能的详细说明**:`useClientRoute` 的更多用法请阅读 [前端页面路由](./client-routes.md)。
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 资源注册
|
|
434
|
+
|
|
435
|
+
以上 Hooks 消费的资源由各模块通过 ServiceDef 声明注册。以下是注册方式的简要说明,详见 [客户端模块开发](./client-module.md)。
|
|
436
|
+
|
|
437
|
+
### 注册组件
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import type { IComponentListService, IComponentListDef } from '@linyjs/client-module-interface';
|
|
441
|
+
|
|
442
|
+
class MyComponentService implements IComponentListService {
|
|
443
|
+
components = {
|
|
444
|
+
MyButton,
|
|
445
|
+
MyCard
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const componentDef: IComponentListDef = {
|
|
450
|
+
serviceTag: 'myComponents',
|
|
451
|
+
serviceImpl: MyComponentService,
|
|
452
|
+
meta: { usageType: 'component' }
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// 注册后可通过 useClientComponent('moduleName.myComponents.MyButton') 获取
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 注册 Hook
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import type { IHooksService, IHooksDef } from '@linyjs/client-module-interface';
|
|
462
|
+
|
|
463
|
+
class MyHooksService implements IHooksService {
|
|
464
|
+
hooks = {
|
|
465
|
+
useFetchData,
|
|
466
|
+
useLocalStorage
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const hookDef: IHooksDef = {
|
|
471
|
+
serviceTag: 'myHooks',
|
|
472
|
+
serviceImpl: MyHooksService,
|
|
473
|
+
meta: { usageType: 'hook' }
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// 注册后可通过 useClientHook('moduleName.myHooks.useFetchData') 获取
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 注册服务
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import type { INormalServiceDef } from '@linyjs/client-module-interface';
|
|
483
|
+
|
|
484
|
+
class MyService {
|
|
485
|
+
doSomething() { /* ... */ }
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const serviceDef: INormalServiceDef = {
|
|
489
|
+
serviceTag: 'myService',
|
|
490
|
+
serviceImpl: MyService,
|
|
491
|
+
meta: { usageType: 'normal_service' }
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// 注册后可通过 useClientService('moduleName.myService') 获取
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## React Context 提供者
|
|
500
|
+
|
|
501
|
+
所有 Hooks 依赖以下 React Context。框架在根组件中提供这些 Context,确保所有子组件都能访问注册表:
|
|
502
|
+
|
|
503
|
+
| Context | 类型 | 说明 |
|
|
504
|
+
|---------|------|------|
|
|
505
|
+
| `ClientComponentsContext` | `Record<string, IComponent>` | 组件注册表 |
|
|
506
|
+
| `ClientHooksContext` | `Record<string, Function>` | Hook 注册表 |
|
|
507
|
+
| `ClientServicesContext` | `Record<string, any>` | 服务注册表 |
|
|
508
|
+
| `ClientRouterContext` | `any` | 路由适配器实例 |
|
|
509
|
+
|
|
510
|
+
> ⚠️ **SSR 注意**:在 SSR 模式下,这些 Context 由 SSR 中间件自动提供。在 CSR 模式下,需要确保根组件包裹了所有 Provider。
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 最佳实践
|
|
515
|
+
|
|
516
|
+
### 1. 使用 TypeScript 泛型获取类型安全
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// ✅ 好的做法:使用泛型参数
|
|
520
|
+
const Avatar = useClientComponent<{ src: string; size?: number }>(
|
|
521
|
+
'userModule.userComponents.Avatar'
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
const useAuth = useClientHook<() => { user: User | null }>(
|
|
525
|
+
'authModule.authHooks.useAuth'
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const analytics = useClientService<IAnalyticsService>(
|
|
529
|
+
'analyticsModule.analyticsService'
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
// ❌ 避免:使用 any 类型,丢失类型检查
|
|
533
|
+
const Avatar = useClientComponent('userModule.userComponents.Avatar'); // 类型为 any
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### 2. 处理资源未找到的情况
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
function MyPage() {
|
|
540
|
+
const Avatar = useClientComponent('userModule.userComponents.Avatar');
|
|
541
|
+
|
|
542
|
+
// 始终检查 undefined
|
|
543
|
+
if (!Avatar) {
|
|
544
|
+
return <div>组件加载中...</div>;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return <Avatar src="/avatar.jpg" />;
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### 3. 避免在渲染期间调用获取到的 Hook
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
function MyComponent() {
|
|
555
|
+
const useAuth = useClientHook('authModule.authHooks.useAuth');
|
|
556
|
+
|
|
557
|
+
// ✅ 正确:先获取 Hook,再在组件顶层调用
|
|
558
|
+
const { user } = useAuth ? useAuth() : { user: null };
|
|
559
|
+
|
|
560
|
+
return <div>{user?.name}</div>;
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 4. 使用完整的跨模块引用路径
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// ✅ 推荐:使用完整路径,明确来源
|
|
568
|
+
const Avatar = useClientComponent('userModule.userComponents.Avatar');
|
|
569
|
+
|
|
570
|
+
// ⚠️ 谨慎:模块内引用依赖自动补全,可能在跨模块场景下失效
|
|
571
|
+
const Avatar = useClientComponent('userComponents.Avatar');
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## 常见问题
|
|
577
|
+
|
|
578
|
+
### 1. Hook 返回 undefined
|
|
579
|
+
|
|
580
|
+
**原因**:资源未注册或引用路径错误。
|
|
581
|
+
|
|
582
|
+
**排查步骤**:
|
|
583
|
+
1. 检查控制台是否有 `[useClientComponent] Component not found` 等警告
|
|
584
|
+
2. 确认模块已正确加载(检查模块加载日志)
|
|
585
|
+
3. 确认引用路径格式正确(三段式 `moduleName.serviceTag.name`)
|
|
586
|
+
4. 确认 ServiceDef 的 `meta.usageType` 正确
|
|
587
|
+
|
|
588
|
+
### 2. 服务引用格式错误
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// ❌ 错误:服务引用使用了三段式
|
|
592
|
+
const service = useClientService('moduleName.serviceTag.someMethod');
|
|
593
|
+
|
|
594
|
+
// ✅ 正确:服务引用使用两段式
|
|
595
|
+
const service = useClientService('moduleName.serviceTag');
|
|
596
|
+
// 然后通过 service.someMethod() 调用方法
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### 3. 路由功能返回 undefined
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
const useNavigate = useClientRoute('useNavigate');
|
|
603
|
+
|
|
604
|
+
if (!useNavigate) {
|
|
605
|
+
// RouterProvider 未设置或路由适配器未初始化
|
|
606
|
+
// 检查是否在 RouterProvider 内部使用此 Hook
|
|
607
|
+
console.warn('路由功能不可用');
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const navigate = useNavigate();
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 相关文档
|
|
617
|
+
|
|
618
|
+
- 💻 [客户端模块开发](./client-module.md) — 资源注册方式
|
|
619
|
+
- 🛤️ [前端页面路由](./client-routes.md) — `useClientRoute` 详细用法
|
|
620
|
+
- 📊 [前端状态管理](./state-management.md) — Jotai 全局状态管理
|
|
621
|
+
- 🔧 [API 参考](../api-reference/index.md) — Hooks 接口定义
|
|
622
|
+
- 🎨 [UI 扩展插槽](./ui-slots.md) — UI 组件注入机制
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
**上一篇**: [前端页面路由](./client-routes.md) | **下一篇**: [前端状态管理](./state-management.md)
|
|
@@ -164,7 +164,7 @@ class BlogRouteService implements IRouteService {
|
|
|
164
164
|
|
|
165
165
|
## 全局状态(Global State)
|
|
166
166
|
|
|
167
|
-
使用 Jotai
|
|
167
|
+
使用 Jotai 进行状态管理。关于状态管理的完整说明(包括派生状态、跨模块共享、SSR 注意事项等),请阅读 [前端状态管理](./state-management.md)。
|
|
168
168
|
|
|
169
169
|
### 定义状态
|
|
170
170
|
|
|
@@ -595,10 +595,13 @@ function SafeComponent() {
|
|
|
595
595
|
|
|
596
596
|
## 下一步
|
|
597
597
|
|
|
598
|
+
- 🛤️ 阅读 [前端页面路由](./client-routes.md) — 路由定义、参数、守卫、Loader、useClientRoute
|
|
599
|
+
- 🪝 阅读 [公共 Hooks 使用](./client-hooks.md) — useClientComponent、useClientHook、useClientService、useClientRoute
|
|
600
|
+
- 📊 阅读 [前端状态管理](./state-management.md) — Jotai 全局状态管理
|
|
598
601
|
- 🎨 阅读 [UI 扩展插槽](./ui-slots.md)
|
|
599
602
|
- 🔒 阅读 [权限管理](./permissions.md)
|
|
600
603
|
- 💾 阅读 [数据持久化](./data-persistence.md)
|
|
601
604
|
|
|
602
605
|
---
|
|
603
606
|
|
|
604
|
-
**上一篇**: [服务端模块开发](./server-module.md) | **下一篇**: [
|
|
607
|
+
**上一篇**: [服务端模块开发](./server-module.md) | **下一篇**: [前端页面路由](./client-routes.md)
|