类型化 fetch
从 void/client 导入 fetch,获得路由自动补全、请求体检查与推断的响应类型。
预计阅读 3 分钟
基本用法
import { fetch } from 'void/client';
// GET:返回类型由你的处理器推断
const users = await fetch('/api/users');
// POST:请求体按处理器的校验器做类型检查
await fetch('/api/users', {
method: 'POST',
body: { name: 'Alice', email: 'alice@example.com' },
});
// 动态参数按路由模式插入
const user = await fetch('/api/users/:id', {
params: { id: '42' },
});
哪些内容会被检查
客户端会约束请求的每一个部分:
- 路径:只接受
routes/中定义过的路由。拼写错误会在编译期失败。 - 方法:只允许路由文件导出的方法。如果文件只导出
GET,传入method: "POST"会报错。 - body、query、params:当处理器使用
defineHandler.withValidator()时,校验器 schema 成为输入类型,客户端会在调用处强制检查。 - 响应类型:与处理器经过 JSON 序列化后返回的类型一致。
查询参数
GET 请求通过 query 传入搜索参数:
export const GET = defineHandler.withValidator({
query: z.object({ q: z.string(), page: z.string().optional() }),
})((c, { query }) => {
return { results: [], term: query.q };
});
const data = await fetch('/api/search', {
query: { q: 'hello' },
});
// data: { results: never[]; term: string }
错误处理
非 2xx 响应会抛出 FetchError:
import { fetch, FetchError } from 'void/client';
try {
await fetch('/api/users/:id', { params: { id: '999' } });
} catch (e) {
if (e instanceof FetchError) {
console.log(e.status); // 404
console.log(e.data); // 已解析的响应体
console.log(e.response); // 原始 Response
}
}
选项
| 选项 | 类型 | 说明 |
|---|---|---|
method | string | HTTP 方法,默认 GET默认 "GET" |
body | object | 请求体(自动序列化为 JSON) |
query | Record<string, string> | 查询字符串参数 |
params | Record<string, string> | URL 路径参数(`:id` 片段) |
headers | HeadersInit | 额外的请求头 |
signal | AbortSignal | 中止信号 |
baseURL | string | 拼在路径前的 base URL,便于调用外部服务 |
retry | number | 重试次数(GET 默认重试 1 次)默认 1 |
timeout | number | 请求超时时间(毫秒) |
SSR 期间的同构 fetch
fetch() 在服务端渲染与路由处理器中同样可用。调用你自己的应用时不会产生 HTTP 往返:
import { fetch } from 'void/client';
import { defineRender } from 'void';
export default defineRender(async (c, assetTags) => {
// 这里直接调用你的 API 路由,没有任何 HTTP 请求
const data = await fetch('/api/users');
const html = renderToString(<App users={data} />);
return new Response(html, {
headers: { 'content-type': 'text/html' },
});
});