TRPC尽管历史悠久,但在Node.js/TypeScript社区中却广受欢迎。快速采用的主要原因之一来自其出色的轻加权设计 - 没有图谱可以编写,没有发电机可以运行。一切都神奇地奏效,利用打字稿的强大类型推理能力。它是提供最佳开发人员体验的API工具包之一。
但是,其功率也受到类型推理能力的上限的限制。让我们看一个例子。假设我有一个后端服务功能,该功能获取了一篇博客文章,其签名如下:
export type Post = { id: number; title: string; }
export type User = { id: number; name: string; }
export type LoadPostArgs = {
id: number;
withAuthor?: boolean;
};
export type LoadPostResult<T extends LoadPostArgs> =
T['withAuthor'] extends true ? Post & { author: User } : Post;
export function loadPost<T extends LoadPostArgs>(args: T): LoadPostResult<T> {
...
}
此通用函数的独特之处在于,其返回类型“适应”输入类型:
// p1 is typed `Post`
const p1 = loadPost({id: 1});
// p2 is typed `Post & { author: User }`
const p2 = loadPost({id: 1, withAuthor: true});
这种动态打字可带来令人愉悦的自动完成体验,并有助于在编译时捕获错误。
让S通过TRPC路由器公开此功能:
// routers.ts
const appRouter = router({
loadPost: publicProcedure
.input(z.object({ id: z.number(), withAuthor: z.boolean().optional() }))
.query(({ input }) => loadPost(input)),
});
export type AppRouter = typeof appRouter;
然后从客户端消耗它:
const trpc = createTRPCProxyClient<AppRouter>({...});
const p1 = await trpc.loadPost.query({ id: 1 });
const p2 = await trpc.loadPost.query({ id: 1, withAuthor: true });
p1
和p2
均为Post
。动态性丢失了。
为什么会发生?
让我们再次查看通用功能:
export function loadPost<T extends LoadPostArgs>(args: T): LoadPostResult<T> {
...
}
被调用时,从混凝土输入参数的类型中推断出通用类型参数T
(只要满足loadPostargs类型)即可。之后,打字稿编译器可以基于推断的T
进一步推断返回类型。关键是一切都发生在函数调用的上下文中。
尽管TRPC在调用远程API时给出了简单函数调用的错觉,但其情况却大不相同。在服务器端路由器注册期间,从ZOD模式对输入的形状进行静态分析,并且无法定义“通用”路由器,您可以在客户端上使用混凝土类型进行实例化。
要进行这种“动态”通用打字工作,TRPC需要能够在内部内部持有“非验证”的通用功能类型,并在不同的上下文中实例化。这需要一个名为“高态类型”的语言功能尚未实施的语言功能。实际上,该功能请求是在2014年创建的,我们可以庆祝其10年周年纪念日ð。
。Allow classes to be parametric in other parametric classes #1213
这是允许仿制药作为类型参数的建议。目前可以编写单调的特定示例,但是为了编写所有monads所满足的界面,我建议写作
同样,可以编写笛卡尔函数的特定示例,但是为了编写所有笛卡尔函子所满足的界面,我提出写作
参数类型参数可以接受任意数量的参数:
也就是说,当类型参数之后是tilde和自然差异时,应允许将类型参数用作声明其余部分中给定ARITY的通用类型。
现在就像现在的情况一样,在实现此类接口时,应填充通用类型参数:
除了直接允许参数中的通用类型的组成外,我还建议Typedef还支持以这种方式定义通用物(请参阅issue 3086):
typedef 也许 < array <〜>>>
现在客户端键入都很好:
const trpc = createMyTRPCProxyClient({ ... });
// post is typed as `Post & { author: User }`
const post = await trpc.post.findFirst.query({
where: { id: postId },
include: { author: true }
);
类型推导与代码生成
类型推理是轻巧且快速的。您的更改会立即反映在IDE内部,而无需运行代码生成步骤。如果可能的话,它应该是首选方法。但是,当您达到限制时,不要回避倒退到代码生成。对于Zenstack而言,这次后备特别自然,因为TRPC路由器已经来自代码生成。产生更多ð。
没有什么伤害。希望您喜欢阅读并发现方法很有趣。我们构建了Zenstack工具包,认为强大的模式可以带来许多好处,以简化全堆栈应用程序的构建。如果您喜欢这个主意,请查看我们的GitHub页面以获取更多详细信息!