TypeScript中动态访问const对象字面量的实现方案——基于ts-proto生成代码的编码需求
解决ts-proto动态编码通用方法的问题
我明白你遇到的困境——ts-proto自动生成的代码没法修改,想写一个通用的encode函数来处理不同的类型,但动态导入和类型获取的问题一直卡着。下面给你几个可行的方案:
异步版本的通用encode函数(推荐)
首先要明确:ES模块的动态导入import()是异步操作,所以你的encode函数必须是异步的(返回Promise)。我们可以通过动态导入模块后,获取其中与类型名同名的编码对象(就是ts-proto生成的那个包含encode和fromJSON的const):
// 先定义一个通用类型,描述ts-proto生成的类型结构 type TsProtoType<T> = { encode(message: T): Writer; fromJSON(object: any): T; }; async function encode(typeName: string, object: any): Promise<Writer> { // 动态导入对应模块 const module = await import(`/path/to/${typeName}`); // 获取模块中导出的编码工具对象(和类型名同名) const typeCodec = module[typeName] as TsProtoType<any>; // 先把原始对象转成目标类型,再执行编码 const typedMessage = typeCodec.fromJSON(object); return typeCodec.encode(typedMessage); } // 使用方式(需要在async函数中调用) async function runEncoding() { const writer1 = await encode("BasicMessage", { id: 1, name: "Fake" }); const writer2 = await encode("BasicMessagePlus", { id: 1, name: "Fake", email: "fake@fake.com" }); // 这里可以处理生成的Writer对象 } runEncoding();
为什么之前的方案行不通?
- 同步import的误区:你原来的代码里直接写
import(...)没有处理异步,import()返回的是Promise,必须用await或者.then()才能拿到模块内容,同步写法根本获取不到模块导出的对象。 - eval/globalThis的问题:ts-proto生成的模块是ES模块,作用域是隔离的,导出的内容不会自动挂载到
globalThis;而eval只能访问当前作用域的变量,无法直接获取模块导出的内容,除非你手动把每个类型挂载到全局(但你没法修改自动生成的代码,所以这条路走不通)。
优化:添加类型安全(可选)
如果想让函数有更好的类型提示,避免传错字段,可以创建一个类型映射表,把类型名和对应的TS类型关联起来:
// 先导入生成的类型(只导入类型,不会打包代码) import type { BasicMessage } from '/path/to/BasicMessage'; import type { BasicMessagePlus } from '/path/to/BasicMessagePlus'; // 定义类型名到类型的映射 type TypeMap = { BasicMessage: BasicMessage; BasicMessagePlus: BasicMessagePlus; }; // 优化后的encode函数,带类型校验 async function encode<T extends keyof TypeMap>( typeName: T, object: Partial<TypeMap[T]> ): Promise<Writer> { const module = await import(`/path/to/${typeName}`); const typeCodec = module[typeName] as TsProtoType<TypeMap[T]>; const typedMessage = typeCodec.fromJSON(object); return typeCodec.encode(typedMessage); } // 现在调用时,object会有对应的类型提示,传错字段会报错! await encode("BasicMessage", { id: 1, name: "Fake" }); // 正确 await encode("BasicMessage", { id: 1, email: "wrong" }); // TypeScript会报错
同步版本的方案(如果必须同步调用)
如果你的业务场景必须同步返回Writer,可以提前预加载所有需要的模块,把编码对象缓存起来:
type TsProtoType<T> = { encode(message: T): Writer; fromJSON(object: any): T; }; // 缓存已加载的编码对象 const typeCache = new Map<string, TsProtoType<any>>(); // 初始化缓存(建议在应用启动时执行) async function initTypeCache() { // 并行加载所有需要的模块 const [basicModule, plusModule] = await Promise.all([ import('/path/to/BasicMessage'), import('/path/to/BasicMessagePlus') ]); typeCache.set('BasicMessage', basicModule.BasicMessage); typeCache.set('BasicMessagePlus', plusModule.BasicMessagePlus); } // 同步编码函数(必须确保缓存已初始化) function encodeSync(typeName: string, object: any): Writer { const typeCodec = typeCache.get(typeName); if (!typeCodec) { throw new Error(`类型 ${typeName} 未预加载到缓存中`); } const typedMessage = typeCodec.fromJSON(object); return typeCodec.encode(typedMessage); } // 使用方式 async function run() { await initTypeCache(); // 先初始化缓存 const writer1 = encodeSync("BasicMessage", { id: 1, name: "Fake" }); const writer2 = encodeSync("BasicMessagePlus", { id: 1, name: "Fake", email: "fake@fake.com" }); } run();
这个方案的缺点是需要提前知道所有要用到的类型名,但胜在可以同步调用。
内容的提问来源于stack exchange,提问作者Brad Urani
相关产品推荐
相关产品推荐

