【问题标题】:Map interface keys to an array of key value pairs将接口键映射到键值对数组
【发布时间】:2021-08-26 09:57:46
【问题描述】:

给定一些接口

例如

interface Person {
    name: string;
    age: number ;
}

我想创建一个接受以下参数的通用函数


const res = Result.combineValues<Person>(
    { age: 18 },
    { name: 'John Doe' }
);

目前为止


class Result<T> {

    readonly value: T;

    private constructor(value: T) {
        this.value = value;
    }

    public static combineValues<T>(...results: { [key in keyof T]?: Result<T[key]> | T[key] }[]): Result<T> {
        let value: T;
        // ... compute value
        return new Result<T>(value);
    }
}


但问题是它允许未定义的值


const res = Result.combineValues<Person>(
    { age: undefined }, // this should give a compile error because age should be a number or Result<number>
    { name: 'John Doe' }
);

并且它不会验证所有属性都已定义


const res = Result.combineValues<Person>(
    { age: 18 } 
    // should give a compile error because `{name: 'Some Name'}` is missing from argument list
); 

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    Typescript 没有任何方法可以将联合或交集类型分解为其组成部分,或者指定许多未知类型必须“加起来”为已知类型。

    出于这个原因,使函数在 Result 类型上成为泛型并尝试对可变参数元组强制执行条件是行不通的。

    相反:我们在它传递的类型元组上使函数泛型,并根据所有这些类型的交集推断函数的返回类型。然后,您可以通过将函数的返回值分配给 Result&lt;Person&gt; 类型的变量来强制执行编译时约束,这将确保分配有效。

    为了允许 Result 类的实例,我们还指定了一个类型来替换 Result&lt;T&gt; with T 类型的所有成员。

    type ReplaceResultsIn<Obj> = {
      [Key in keyof Obj]: Obj[Key] extends Result<infer T> ? T : Obj[Key]
    }
    type Combine<T extends unknown[]> = T extends [infer First, ...infer Rest] ? (ReplaceResultsIn<First>) & Combine<Rest> : unknown
    

    然后我们输入combineValues 函数返回Combine&lt; the argument types &gt;

    class Result<T> {
      readonly value: T;
    
      public constructor(value: T) {
        this.value = value;
      }
      public static combineValues<T extends object[]>(...args: T): Result<Combine<T>> {
            let value!: Combine<T>;
            // ... compute value
            return new Result<Combine<T>>(value);
        }
    }
    

    这个类的消费者现在可以存在类型安全:

    interface Person {
      name: string;
      age: number;
    };
    
    // these are all OK
    const person: Result<Person> = Result.combineValues({age: new Result(21)}, {name: ""});
    const person2: Result<Person> = Result.combineValues({age: 21, name: ""});
    const person3: Result<Person> = Result.combineValues({age: 21}, {name: ""});
    const person4: Result<Person> = Result.combineValues({name: new Result("")}, {age: new Result(21)});
    
    // these are all bad
    const person5: Result<Person> = Result.combineValues({});
    const person6: Result<Person> = Result.combineValues({age: 21});
    const person7: Result<Person> = Result.combineValues({name: 21}, {age: 21});
    const thing = Result.combineValues("");
    const thing2 = Result.combineValues(null);
    

    Playground link

    【讨论】:

    • 我已经测试了您的解决方案,这是我在类型安全方面所需要的,但它有一个缺点,即 IDE 智能感知/自动完成功能无法建议 Result 中可用的属性.combineValues(...)。还值得一提的是,对于可能使用此解决方案的任何人,我必须将 typescript 版本从 3.9.7 更新到 4.4.2
    • 如果配置正确,您的 IDE 应该能够很好地推断返回类型...您使用的是哪个 IDE?如果是 VSCode,您还应该选择语言服务器使用的打字稿版本与您的工作区相同。
    • 我使用 Webstorm,我相信它配置正确。但您也可以在您提供的 Playground 链接中看到此问题。
    • 你在操场上从哪里得到这个问题?如果我键入 Result.combineValues({age: new Result(21)}, {name: ""}).value.,则尾随句点会显示带有两个预期属性的智能感知:age (number)name (string)
    • Herehere 是 Playground 中的一些示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-11
    • 1970-01-01
    • 2018-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-25
    相关资源
    最近更新 更多