快速开始
这一页把「装包 → 配置 TypeScript → 定义 server → 定义 api → 发请求」整条链路走完, 所有代码片段都可以直接粘贴运行。
1. 安装
axios 是 peer dependency,需要和库一起安装。
pnpm add @snail-js/api axiosnpm install @snail-js/api axiosyarn add @snail-js/api axios不需要 reflect-metadata
@snail-js/api 的 dependencies 里只有 axios。不要再装或 import reflect-metadata, 也不要打开 emitDecoratorMetadata。理由见 TypeScript 配置。
2. 打开 experimentalDecorators
装饰器是本库的使用方式,所以这个开关是必须的:
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "ES2022",
"module": "preserve",
"moduleResolution": "bundler",
"strict": true
}
}完整的配置说明、以及与旧版 0.1.x 文档里那份 tsconfig 的差异,见 TypeScript 配置。
3. 定义 server
一个 server 类 = 一个 axios 实例 + 一份解析后的配置 + 一个插件注册表。继承 SnailServer, 用 @Server(...) 装饰,然后实例化一次并导出。
// src/service.ts
import { Server, SnailServer } from "@snail-js/api";
@Server({
baseURL: "/api",
timeout: 10000
})
class BackEnd extends SnailServer {}
export const Service = new BackEnd();可选的简写形式 @Server("/api") 等价于 @Server({ baseURL: "/api" })。
三个容易踩的点
@Server(...)必须装饰在extends SnailServer的子类上 —— 选项是在SnailServer构造函数里通过this.constructor读出来的。- 忘了
@Server(...)会在实例化时直接抛SnailOptionsError:服务类[BackEnd]缺少 @Server() 装饰器。 baseURL不能是空字符串,@Server({ baseURL: "" })会抛@Server() 的 baseURL 必须是非空字符串。省略该字段时默认是"/"。
4. 定义 api
api 类用 @Api(url) 装饰,方法用请求方式装饰器标记。被标记的方法体永远不会执行, 它只用来声明参数和返回类型 —— 所以约定写 return null!;。
// src/user.api.ts
import { Api, Data, Get, Params, Post, Query } from "@snail-js/api";
import { Service } from "./service";
export interface User {
id: number;
name: string;
}
@Api("/user")
class UserApi {
/** GET /api/user/:id */
@Get("/:id")
getUser(@Params("id") id: string): Promise<User> {
return null!;
}
/** GET /api/user?page=1&size=20 */
@Get("/")
list(
@Query() page: { page: number; size: number }
): Promise<{ total: number; items: User[] }> {
return null!;
}
/** POST /api/user */
@Post("/")
create(@Data() body: { name: string }): Promise<User> {
return null!;
}
}
export const userApi = Service.createApi(UserApi);Service.createApi(UserApi) 返回一个代理。代理方法被调用时只构造请求对象,不发请求, 所以返回的是 SnailMethod。
5. 发请求
const { data, code, message, fromCache } = await userApi.getUser("42").send();
console.log(data.name);send() 解析成 SnailResult,字段含义见 响应与类型。
如果需要「拿到句柄 → 订阅事件 → 稍后再发」(表单提交、按钮点击等场景):
const method = userApi.create({ name: "ada" });
method.onSuccess((result) => console.log("ok", result.data));
method.onError((error) => console.error("failed", error));
method.onFinish(() => console.log("settled"));
await method.send();send() 也可以覆盖代理时传入的参数:
const bound = userApi.getUser("from-proxy");
await bound.send(); // GET /api/user/from-proxy
await bound.send("other"); // GET /api/user/other6. 完整的最小可运行文件
把下面这段单独跑起来(需要一个返回 { code: 0, message: "ok", data: {...} } 的后端, 或者把 adapter 换成 测试用适配器):
import {
Api,
Get,
Params,
Query,
Server,
SnailServer,
type SnailResult
} from "@snail-js/api";
@Server({ baseURL: "/api", timeout: 10000 })
class BackEnd extends SnailServer {}
const Service = new BackEnd();
@Api("/user")
class UserApi {
@Get("/:id")
getUser(
@Params("id") id: string,
@Query("withProfile") withProfile?: boolean
): Promise<{ id: number; name: string }> {
return null!;
}
}
const userApi = Service.createApi(UserApi);
async function main(): Promise<void> {
const result: SnailResult<
{ code: number; message: string; data: unknown },
{ id: number; name: string }
> = await userApi.getUser("1", true).send();
console.log(result.code, result.message, result.data, result.fromCache);
}
void main();