公司动态
158、【Agent】【OpenCode】TuiThreadCmd(RPC 泛型推导)
【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除标题158、【Agent】【OpenCode】TuiThreadCmdRPC 泛型推导背景上篇 blog【Agent】【OpenCode】TuiThreadCmdRPC分析了 OpenCode 默认的子命令 TuiThreadCmd其核心作用是把当前进程中的 fetch 网络请求通过 RPC 通道转发给另一个进程Worker去真正执行通常用于 CLI 或桌面应用中主进程不直接发网络请求而是委托给专门的 Worker 进程处理接着分析了 RPC 的含义以及为什么需要 RPC一句话总结RPC 把网络请求伪装成本地函数调用开发者只管调函数、传参数、拿结果底层的序列化、传输、路由全由框架自动完成下面继续分析OpenCode下面继续分析22 行这里声明了 RpcClient 自动推导的类型其中这里的尖括号是 TypeScript 的泛型Generics语法可以把它理解为 类型参数就像函数接收值参数一样泛型接收的是类型参数。其作用是让一个通用的类型/函数能够根据你传入的具体类型动态地推导出精确的结果。下面由内向外拆解这行代码type RpcClientReturnTypetypeofRpc.clienttypeofrpc1. 最内层typeof rpcrpc是一个具体的 RPC 服务定义对象比如定义了fetch、log等方法及其参数/返回值类型。而typeof rpc获取了这个对象的完整类型信息。2. 中间层Rpc.clienttypeof rpcRpc.client是一个泛型函数/类型它需要一个类型参数来知道要为哪个 RPC 服务生成客户端。typeof rpc就是把刚才获取的服务定义类型喂给Rpc.client。结果Rpc.client根据 rpc 的定义生成了一个专属于这个服务的客户端类型包含call(fetch, ...)等方法的精确签名。3. 最外层ReturnType...ReturnType是 TypeScript 内置的工具类型作用是提取一个函数的返回值类型。...里放的就是上一步生成的客户端构造函数类型。结果拿到了调用Rpc.clienttypeof rpc()后返回的那个客户端实例的类型。 类比理解如果把泛型比作一台榨汁机概念类比本例中的对应泛型榨汁机的投料口告诉机器要处理什么水果类型参数放入的水果typeof rpcRPC 服务定义泛型函数/类型榨汁机本身Rpc.client/ReturnType输出类型榨出的果汁精确的客户端类型RpcClient为什么不直接写死类型因为Rpc.client是一个通用工厂它可以为任意 RPC 服务生成客户端。如果不传typeof rpcTypeScript 就不知道要生成哪个服务的客户端也就无法提供call(fetch, ...)的参数提示和类型检查。一句话总结这里的就是在告诉 TypeScript请根据提供的 rpc 服务定义精确推导出对应的客户端类型从而实现零手写接口的类型安全 RPC 调用。如果这个项目永远只有一个 RPC 服务定义可以直接写死类型完全不需要泛型。但Rpc.client之所以设计成泛型是因为它是一个通用的 RPC 框架/库而不是为某个具体业务量身定制的代码。下面从两个维度来分析1. 为什么不能直接把类型定义成typeof rpc因为Rpc.client是造客户端的工厂它不知道也不应该知道具体的业务是什么。假设项目里现在有一个 rpc包含fetch方法未来可能还会加一个用于数据库操作的dbRpc包含query方法// 业务 A 的网络 RPCconstrpcdefineRpc({fetch:...})// 业务 B 的数据库 RPCconstdbRpcdefineRpc({query:...})如果用泛型Rpc.clienttypeof rpc生成网络客户端Rpc.clienttypeof dbRpc生成数据库客户端。同一个工厂生产不同产品类型完全精确。如果写死typeof rpcRpc.client就只能生成网络客户端。当想要数据库客户端时要么报错说没有query方法要么得把类型改成any丧失类型安全要么得复制粘贴一份一模一样的工厂代码改名叫DbRpcClient。核心原则框架代码与业务代码解耦。Rpc.client属于框架层rpc属于业务层。框架层绝不能反向依赖业务层的具体类型。2.rpc的类型不是固定的一般的固定是指在运行时rpc这个变量指向的对象确实是确定的。但在 TypeScript 的类型世界里情况完全不同① rpc 没有显式类型注解通常 RPC 服务定义是这样的constrpcdefineRpc({fetch:{/* ... */},log:{/* ... */}})开发者不会也不应该手动写一个巨大的interface来描述它而是依赖defineRpc自动推导。这意味着rpc的类型是隐式的、复杂的、且可能随业务变动而变动的。② typeof 是连接值与类型的桥梁在 TypeScript 中值和类型是两个独立的命名空间。rpc是一个运行时的值泛型需要的是一个类型不能直接把值塞进尖括号里Rpc.clientrpc❌ 语法错误必须用typeof把值转换成类型Rpc.clienttypeof rpc✅③ 避免类型同步地狱如果不使用typeof rpc就必须手动维护一个类型// 每次给 rpc 加新方法都要在这里同步修改interfaceIRpcDefinition{fetch:{input:{...};output:{...}}log:{input:{...};output:{...}}}type RpcClientReturnTypetypeofRpc.clientIRpcDefinition这就违背了 TypeScript 的核心优势——类型推导。使用typeof rpc只需修改rpc的定义客户端类型就会自动、实时、零误差地更新。 总结对比方案适用场景缺点写死typeof rpc整个空间只有一个 RPC 服务框架与业务耦合无法复用手写 Interface需要跨文件共享类型需手动同步容易遗漏出错泛型 typeof通用框架 多业务场景无缺点业界标准实践一句话总结Rpc.client用泛型是因为它是通用框架而非业务代码用typeof rpc是因为 TypeScript 中值和类型分离且这是让客户端类型跟随业务定义自动推导的正确方式。OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog