|
|
@@ -6,22 +6,23 @@
|
|
|
| 1.0 | 2026-01-03 | 初始版本,基于故事010.003修复经验总结 | James (Claude Code) |
|
|
|
| 1.1 | 2026-01-03 | 添加中线(kebab-case)路由规范 | James (Claude Code) |
|
|
|
| 1.2 | 2026-01-03 | 明确规范同样适用于前端RPC client (hc) | James (Claude Code) |
|
|
|
+| 1.3 | 2026-01-03 | 修正前端使用rpcClient而非直接使用hc | James (Claude Code) |
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
本文档定义了使用 Hono RPC 客户端时的正确调用方式,包括:
|
|
|
-- **测试环境**: `hono/testing` 的 `testClient` - 用于集成测试
|
|
|
-- **生产环境**: `@hono/rpc` 的 `hc` - 用于前端 RPC 调用
|
|
|
+- **测试环境**: `hono/testing` 的 `testClient` - 用于后端集成测试
|
|
|
+- **生产环境**: `@d8d/shared-ui-components/utils/hc` 的 `rpcClient` - 用于前端 UI 包 RPC 调用
|
|
|
|
|
|
两者的路径映射规则完全一致,本文档的规范适用于所有 Hono RPC 客户端。
|
|
|
|
|
|
## 基本原理
|
|
|
|
|
|
-Hono RPC 客户端(包括 `testClient` 和 `hc`)会根据路由的路径结构自动生成类型安全的客户端对象。关键是理解路径到属性的映射规则。
|
|
|
+Hono RPC 客户端(包括 `testClient` 和 `rpcClient`)会根据路由的路径结构自动生成类型安全的客户端对象。关键是理解路径到属性的映射规则。
|
|
|
|
|
|
**所有 Hono RPC 客户端共享相同的路径映射规则**,无论是:
|
|
|
- 后端测试使用的 `testClient`(来自 `hono/testing`)
|
|
|
-- 前端调用使用的 `hc`(来自 `@hono/rpc`)
|
|
|
+- 前端 UI 包使用的 `rpcClient`(来自 `@d8d/shared-ui-components/utils/hc`)
|
|
|
|
|
|
### 核心映射规则
|
|
|
|
|
|
@@ -165,45 +166,60 @@ app.openapi(route, handler);
|
|
|
const response = await client.admin['unified-advertisement-types'].$get();
|
|
|
```
|
|
|
|
|
|
-### 5. 前端 RPC Client (hc) 使用
|
|
|
+### 5. 前端 UI 包 RPC Client 使用
|
|
|
|
|
|
-**重要**: 前端使用 `hc` 创建的 RPC 客户端与 `testClient` 使用**完全相同的路径映射规则**。
|
|
|
+**重要**: 前端 UI 包使用 `rpcClient` 创建的 RPC 客户端与 `testClient` 使用**完全相同的路径映射规则**。
|
|
|
|
|
|
```typescript
|
|
|
-// 前端 API 客户端创建
|
|
|
-import { hc } from 'hono/client';
|
|
|
-import type { ApiRoutes } from '@d8d/server';
|
|
|
-
|
|
|
-export const apiClient = hc<ApiRoutes>('/api/v1');
|
|
|
-
|
|
|
-// 路由定义: path: '/' (在 server 包注册为 '/api/v1/admin/unified-advertisements')
|
|
|
-const response = await apiClient.admin['unified-advertisements'].$get({
|
|
|
+// 前端 API 客户端管理器(UI包标准模式)
|
|
|
+// src/api/<module>Client.ts
|
|
|
+import { <module>Routes } from '@d8d/<module-name>-module';
|
|
|
+import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
|
|
|
+
|
|
|
+export class <Module>ClientManager {
|
|
|
+ private client: ReturnType<typeof rpcClient<typeof <module>Routes>> | null = null;
|
|
|
+
|
|
|
+ public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof <module>Routes>> {
|
|
|
+ return this.client = rpcClient<typeof <module>Routes>(baseUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ public get(): ReturnType<typeof rpcClient<typeof <module>Routes>> {
|
|
|
+ if (!this.client) {
|
|
|
+ return this.init();
|
|
|
+ }
|
|
|
+ return this.client;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const <module>ClientManager = <Module>ClientManager.getInstance();
|
|
|
+const client = <module>ClientManager.get();
|
|
|
+
|
|
|
+// 路由定义: path: '/' (在 server 包注册为 '/api/v1/admin/<module>s')
|
|
|
+const response = await client.index.$get({
|
|
|
query: { page: 1, pageSize: 10 }
|
|
|
});
|
|
|
|
|
|
// 路由定义: path: '/:id'
|
|
|
-const response = await apiClient.admin['unified-advertisements'][':id'].$put({
|
|
|
+const response = await client[':id'].$put({
|
|
|
param: { id: 123 },
|
|
|
json: updateData
|
|
|
});
|
|
|
```
|
|
|
|
|
|
**关键点**:
|
|
|
-1. **hc 调用路径与 testClient 完全一致**: 如果测试用 `adminClient.$get()`,前端也用 `apiClient.admin.xxx.$get()`
|
|
|
-2. **不需要重复前缀**: hc 已经配置了 baseURL (`/api/v1`),调用时不需要再包含
|
|
|
+1. **rpcClient 调用路径与 testClient 完全一致**: 如果测试用 `adminClient.$get()`,前端也用 `client.index.$get()`
|
|
|
+2. **不需要重复前缀**: `rpcClient` 会配置 baseURL,调用时只需要使用路由的相对路径
|
|
|
3. **类型安全**: TypeScript 会自动推断正确的路径和方法
|
|
|
|
|
|
-**错误示例**:
|
|
|
-```typescript
|
|
|
-// ❌ 错误: 重复包含 /api/v1 前缀
|
|
|
-const response = await apiClient['/api/v1/admin/unified-advertisements'].$get();
|
|
|
+**路由名称对应关系**:
|
|
|
+| 模块内路由定义 | Server注册 | rpcClient调用 |
|
|
|
+|--------------|-----------|--------------|
|
|
|
+| `path: '/'` | `.route('/api/v1/admin/<module>s', routes)` | `client.index.$get()` |
|
|
|
+| `path: '/:id'` | `.route('/api/v1/admin/<module>s', routes)` | `client[':id'].$get()` |
|
|
|
+| `path: '/search'` | 自定义路由 | `client.search.$get()` |
|
|
|
|
|
|
-// ❌ 错误: 中线路径用点号访问
|
|
|
-const response = await apiClient.admin.unified-advertisements.$get();
|
|
|
-
|
|
|
-// ✅ 正确: 使用方括号访问中线路径
|
|
|
-const response = await apiClient.admin['unified-advertisements'].$get();
|
|
|
-```
|
|
|
+**参考实现**: UI包开发规范 - RPC客户端实现规范
|
|
|
|
|
|
## 常见错误 ❌
|
|
|
|
|
|
@@ -413,13 +429,14 @@ await client.$get() // path: '/' → .$get()
|
|
|
|
|
|
- [后端模块包开发规范](./backend-module-package-standards.md) - 路由定义规范
|
|
|
- [后端模块包测试规范](./backend-module-testing-standards.md) - 测试框架和集成测试
|
|
|
+- [UI包开发规范](./ui-package-standards.md) - RPC客户端实现规范
|
|
|
- [编码标准](./coding-standards.md) - RPC API测试规范章节
|
|
|
|
|
|
---
|
|
|
|
|
|
**文档状态**: 正式版
|
|
|
**创建原因**: 故事010.003修复过程中发现缺乏testClient调用规范
|
|
|
-**适用范围**: 所有使用 Hono RPC 客户端的地方(testClient 和 hc)
|
|
|
+**适用范围**: 所有使用 Hono RPC 客户端的地方
|
|
|
**覆盖范围**:
|
|
|
- 后端集成测试 (`hono/testing` 的 `testClient`)
|
|
|
-- 前端 UI 包 RPC 调用 (`@hono/rpc` 的 `hc`)
|
|
|
+- 前端 UI 包 RPC 调用 (`@d8d/shared-ui-components/utils/hc` 的 `rpcClient`)
|