如何为可管道类的pipe方法添加类型约束,仅允许解构已添加属性?
实现类型安全的链式Pipe字典填充类
问题描述
我有一个包含字典的类,可通过pipe链式调用特定函数填充字典:函数接收当前字典对象,解构已有属性作为参数,返回包含新属性的对象(新属性会被添加到字典中)。需要给pipe函数添加类型定义,使其仅允许解构此前已添加的属性,避免出现像示例中访问不存在属性却不报错的情况。
原示例代码(存在类型不安全问题):
type AugmentFun = (source: Record<string, any>) => Record<string, any>; class Pipeable { constructor(private dict: Record<string, any>) {} static of = (dict: Record<string, any>) => new Pipeable(dict); static augment = (source: Pipeable, fun: AugmentFun) => { return Pipeable.of(Object.assign({}, source.dict, fun(source.dict))); }; pipe = (...funs: AugmentFun[]) => funs.reduce(Pipeable.augment, this); } const p = new Pipeable({}); // 合法场景,不应报错 const res1 = p.pipe( () => ({ a: 1, b: 2 }), // { a: 1, b: 2 } ({ a, b }) => ({ c: a + b }), // { a: 1, b: 2, c: 3} ({ c }) => ({ d: c ** 2 }) // { a: 1, b: 2, c: 3, d: 9} ); // 非法场景,应报错但未报错 const res2 = p.pipe( () => ({ a: 1, b: 2 }), // { a: 1, b: 2 } ({ c }) => ({ d: c + 10 }) // 错误:"c"不存在于当前字典中 );
解决方案:类型安全的实现
通过泛型跟踪字典类型和分步约束函数输入输出,可以实现严格的类型检查:
// 定义增强函数类型:输入当前字典T,返回新增属性U type AugmentFun<T, U extends Record<string, unknown>> = (source: T) => U; // 定义Reduce类型,用于串联多个增强函数的类型 type PipeReduce<T, Fs extends any[]> = Fs extends [ infer First, ...infer Rest ] ? First extends AugmentFun<infer Input, infer Output> ? PipeReduce<Input & Output, Rest> : T : T; class Pipeable<T extends Record<string, unknown> = {}> { constructor(private dict: T) {} static of = <U extends Record<string, unknown>>(dict: U) => new Pipeable(dict); static augment = <S extends Record<string, unknown>, U extends Record<string, unknown>>( source: Pipeable<S>, fun: AugmentFun<S, U> ): Pipeable<S & U> => { return Pipeable.of({ ...source.dict, ...fun(source.dict) }); }; pipe = <Fs extends AugmentFun<any, any>[]>( ...funs: Fs ): Pipeable<PipeReduce<T, Fs>> => { return funs.reduce((acc, fun) => Pipeable.augment(acc, fun), this) as Pipeable<PipeReduce<T, Fs>>; }; }
关键说明
- 泛型跟踪字典类型:
Pipeable<T>中的T会实时跟踪当前字典的所有属性类型,替代原本模糊的Record<string, any> - 增强函数类型约束:
AugmentFun<T, U>明确要求函数的输入是当前的字典类型T,输出是新增的属性U,确保函数只能解构T中已存在的属性 - PipeReduce类型串联:
PipeReduce<T, Fs>会递归遍历所有pipe函数,依次将前一个函数的输出(新增属性)合并到当前字典类型中,确保后续函数只能访问之前步骤已添加的属性 - 类型安全的augment方法:
augment方法会返回合并了原属性和新增属性的Pipeable<S & U>,保证类型的正确传递
效果验证
使用上述实现后:
- 合法的
res1不会出现类型错误,且res1的类型会被推断为Pipeable<{a: number; b: number; c: number; d: number}> - 非法的
res2中,({ c })会直接抛出类型错误:属性"c"不存在于类型"{ a: number; b: number; }"中,符合预期
内容的提问来源于stack exchange,提问作者Adam B.
相关产品推荐
相关产品推荐

