使用生成代码
实现 HTTP 客户端接口
生成的客户端层不绑定框架。只要实现一次 HttpClient 接口,接上项目里现成的 HTTP 库就行:
typescript
import { HttpClient, RequestConfig } from './generated/clients/http-client';
class MyHttpClient implements HttpClient {
async get<T>(url: string, params?: any, config?: RequestConfig): Promise<T> {
const response = await fetch(url, {
method: 'GET',
headers: config?.headers,
signal: config?.signal,
});
return response.json();
}
async post<T>(url: string, data?: any, config?: RequestConfig): Promise<T> {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...config?.headers,
},
body: JSON.stringify(data),
signal: config?.signal,
});
return response.json();
}
// 实现 put、delete、patch 方法...
}使用生成的客户端
typescript
import { UserServiceClient } from './generated/clients/user-client';
import { MyHttpClient } from './http-client';
const httpClient = new MyHttpClient();
// 不传前缀:proto 已经声明了完整路径(/api/v1/users/{user_id})
const userService = new UserServiceClient(httpClient);
// 调用 API
const user = await userService.getUser({ userId: '123' });
console.log(user);baseUrl 参数
客户端构造函数接受可选的第二个参数 baseUrl,会拼接到每个 proto 声明的请求路径前面。默认是空字符串,也就是直接用 proto 里声明的路径发请求。
只有当前端请求路径和 proto 声明的不一致时才需要传——典型场景是网关(比如 Nginx)在转发时加了前缀。比如 proto 声明的是 /user/get,Nginx 把所有请求都改写成 /api/user/get,那就传 /api:
typescript
// 后端 proto:rpc GetUser(...) { option (google.api.http) = { get: "/user/get" }; }
// 前端走 Nginx 转发,统一加了 /api → /api/user/get
const userService = new UserServiceClient(httpClient, '/api');如果前后端路径完全一致,就不用传。
响应类型解包
生成的客户端支持解包 { code, data, message } 这类响应包装。传入包装类型和实际数据所在的字段名即可:
typescript
// 定义响应包装类型
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
// 使用泛型参数自动解包
const userService = new UserServiceClient<ApiResponse<any>, 'data'>(httpClient);
// 返回类型自动推断为 User,而非 ApiResponse<User>
const user = await userService.getUser({ userId: '123' });三个泛型参数:
TRes- 响应包装类型。void(默认)表示不包装,返回原始响应类型。传入业务包装类型可自动解包。TDataKey- 包装类型里实际数据所在的字段名。默认'data',可设为'payload'、'result'、'body'等。TExtras-RequestConfig.extras的类型约束。默认无约束;传入具体类型(如AxiosRequestConfig)可对框架专属选项做完整类型检查。
请求级配置
每个方法都接受可选的 RequestConfig,按请求单独控制行为:
typescript
await userService.getUser(
{ userId: '123' },
{ silent: true, signal: controller.signal, headers: { 'X-Trace-Id': 'abc' } },
);silent- 在请求级别静默错误(不弹 UI 提示)signal- AbortController 信号,用于取消请求headers- 自定义请求头responseType- 覆盖响应处理方式(json、blob、text、arraybuffer)extras- 框架专属配置,由你的适配层解析metadata- 声明式请求元数据,供你的 HttpClient 实现据此做路由决策