如何在TypeScript中实现类似JS的多类型参数映射式处理?
问题
我有一个可接收多种类型参数的函数(具体是Proxy的get处理器),需要根据参数类型执行差异化处理。在原生JavaScript中,可以通过构造函数名称映射对应处理函数实现,代码如下:
const calls = { "String": (str)=>{/*...*/}, "Number": (num)=>{/*...*/}, "Symbol": (symb)=>{/*...*/}, "Object": (obj)=>{/*...*/} } function foo(obj){ const name = Object.getPrototypeOf(obj).constructor.name; if (name in calls) { calls[name](obj); } else { /*未知类型处理*/ } }
但在TypeScript中,为了保留类型追踪,目前只能想到链式类型守卫的方案,代码如下:
// isNumber, isString, isSymbol, isObject - 类型守卫(type guard) function foo(obj: string | number | symbol):void { if (isString(obj)){ /*...*/ } else if (isNumber(obj)) { /*...*/ } else if (isSymbol(obj)) { /*...*/ } else if (isObject(obj)) { /*...*/ } else { /*未知类型处理*/ } }
这个方案相比JS实现显得冗余,类型增多时会逐渐失控,还存在不必要的分支判断。想询问:TypeScript中是否存在与该JS方案等价的实现方式?
我还提供了更具体的场景示例:
class Container { constructor(){} metaData = {}; props = new Map(); } const valuePreProcess = { "Number"(target, prop, value){ return value.toString() }, "Container"(target, prop, value){ value.metaData.parent = target; return value; } } const handlers = { set(target, prop, value){ // 需要确定value的类型,才能正确调用对应的处理函数 const processedValue = valuePreProcess[<typeOfValue>](target, prop, value); target.props.set(prop, processedValue); } } const obj = new Proxy(new Container(),handlers);
还有一种使用原型作为键的映射思路:
const valuePreProcessors = new Map([ [Number.prototype,(target, prop, value)=>value.toString()], [Container.prototype,(target, prop, value)=>{ value.metaData.parent = target; return value; }] ]); const handlers = { set(target, prop, value){ const processFunc = valuePreProcessors.get(Object.getPrototypeOf(value)); target.props.set(prop, processFunc(target, prop, value)); } }
解决方案
在TypeScript中,完全可以实现和JS等价的映射式类型处理,同时保留完整的类型追踪。下面是几种可行的方案:
1. 基于构造函数名称的类型安全映射
通过定义类型到处理函数的映射类型,结合类型断言和索引访问,实现类型安全的调用:
// 先定义支持的类型集合 type SupportedType = string | number | symbol | Container; // 定义处理函数的类型签名,根据输入类型返回对应结果 type ProcessorMap = { String: (target: Container, prop: PropertyKey, value: string) => string; Number: (target: Container, prop: PropertyKey, value: number) => string; Symbol: (target: Container, prop: PropertyKey, value: symbol) => symbol; Container: (target: Container, prop: PropertyKey, value: Container) => Container; }; const valuePreProcess: ProcessorMap = { String: (target, prop, value) => value.toUpperCase(), // 示例处理逻辑 Number: (target, prop, value) => value.toString(16), Symbol: (target, prop, value) => Symbol.for(value.description || ""), Container: (target, prop, value) => { value.metaData.parent = target; return value; }, }; class Container { metaData = {}; props = new Map<PropertyKey, unknown>(); } const handlers = { set(target: Container, prop: PropertyKey, value: SupportedType): boolean { // 获取构造函数名称,并断言为ProcessorMap的键类型 const constructorName = (Object.getPrototypeOf(value).constructor.name as keyof ProcessorMap); const processor = valuePreProcess[constructorName]; if (processor) { const processedValue = processor(target, prop, value as never); target.props.set(prop, processedValue); return true; } // 未知类型处理 target.props.set(prop, value); return true; }, }; const obj = new Proxy(new Container(), handlers);
这种方式和你原来的JS逻辑几乎一致,同时通过ProcessorMap和SupportedType约束了类型,确保处理函数的输入输出类型正确。
2. 基于原型的Map方案(增强类型安全)
你提到的原型作为键的思路可以优化为类型安全版本,通过定义Map的键值类型,避免不必要的类型断言:
class Container { metaData = {}; props = new Map<PropertyKey, unknown>(); } // 定义处理器的类型签名 type ValueProcessor = (target: Container, prop: PropertyKey, value: any) => unknown; // 定义Map的键为构造函数原型,值为对应处理器 const valuePreProcessors = new Map<object, ValueProcessor>([ [String.prototype, (target, prop, value: string) => value.toLowerCase()], [Number.prototype, (target, prop, value: number) => value.toFixed(2)], [Container.prototype, (target, prop, value: Container) => { value.metaData.parent = target; return value; }], ]); const handlers = { set(target: Container, prop: PropertyKey, value: unknown): boolean { const proto = Object.getPrototypeOf(value); const processor = valuePreProcessors.get(proto); if (processor) { target.props.set(prop, processor(target, prop, value)); return true; } // 未知类型处理 target.props.set(prop, value); return true; }, }; const obj = new Proxy(new Container(), handlers);
这里通过Map<object, ValueProcessor>明确了键的类型为原型对象,同时在处理器内部可以针对具体类型做类型约束,保证类型安全。
3. 利用类型谓词自动推断类型
如果需要更严格的类型推断,可以结合类型谓词和映射,让TypeScript自动推导处理函数的参数类型:
class Container { metaData = {}; props = new Map<PropertyKey, unknown>(); } // 类型谓词集合 function isString(value: unknown): value is string { return typeof value === "string"; } function isNumber(value: unknown): value is number { return typeof value === "number"; } function isContainer(value: unknown): value is Container { return value instanceof Container; } // 定义类型到处理器的映射,同时关联类型谓词 const typeProcessors = [ { predicate: isString, handler: (target: Container, prop: PropertyKey, value: string) => value.toUpperCase() }, { predicate: isNumber, handler: (target: Container, prop: PropertyKey, value: number) => value.toString() }, { predicate: isContainer, handler: (target: Container, prop: PropertyKey, value: Container) => { value.metaData.parent = target; return value; } }, ]; const handlers = { set(target: Container, prop: PropertyKey, value: unknown): boolean { const processor = typeProcessors.find(item => item.predicate(value)); if (processor) { target.props.set(prop, processor.handler(target, prop, value as never)); return true; } target.props.set(prop, value); return true; }, }; const obj = new Proxy(new Container(), handlers);
这种方式兼顾了映射的简洁性和类型守卫的严格性,类型增多时只需要添加新的{ predicate, handler }条目即可,比链式else if更易维护。
内容的提问来源于stack exchange,提问作者vanilla
相关产品推荐
相关产品推荐

