Typescript 类型推断和可选的对象属性

时间:2021-01-30 19:20:37

标签: typescript constructor optional record inference

基本上我想要实现的是让类 X 从提供的构造函数参数类型推断其 K 类型。

我写了这个:

interface XContstructorParams<K extends string = never> {
    record?: Record<K, number>;
    // Other stuff with some being optionals, which is why it's all in single
    // object, as I don't want to do new X(arg1, undefined, undefined, arg2)
}

class X<K extends string = never> {
    private record: Record<K, number>;
    constructor(params: XContstructorParams<K>) {
        // I'd rather avoid to use a type assertion here but it's not a big deal
        // compared to the issue below.
        this.record = params.record || {} as Record<K, number>;
    }
    
    getNumber(k: K): number {
        return this.record[k];
    }
}

这实际上可以在未提供 record 属性时推断 K 的类型为 never,但它不会阻止手动为 K 指定不正确的值:

// Correctly inferred to be X<'a', 'b'>
const x1 = new X({ record: {'a': 1, 'b': 2} });
// Correctly inferred to be X<never>
const x2 = new X({});
// Oops ! This is incorrect as `getNumber` will not return a number when called with 'c'
// or 'd ! But it is allowed because of how I wrote the constructor `record` property to
// be optional. I want it to be optional only when K is never. It's either you provided
// a record and it's keys determine K, or you didn't give a record and K is never, but
// should not be able to specify K and not provide a record matching your specified K.
const x3 = new X<'c' | 'd'>({});

我希望将 x3 识别为编译错误,但我无法使其正常工作。我做错了什么?

编辑:出于某种原因,堆栈溢出在消息的开头删除了我的“你好”,所以......我猜迟到总比没有好......你好,感谢您的帮助! ;-)

1 个答案:

答案 0 :(得分:0)

此解决方案适合您吗?

interface XContstructorParams<K extends string = never> {
  record: K extends never ? undefined : Record<K, number>;
  // Other stuff with some being optionals, which is why it's all in single
  // object, as I don't want to do new X(arg1, undefined, undefined, arg2)
}

class X<K extends string = never> {
  private record: Record<K, number>;
  constructor(params: XContstructorParams<K>) {
    // I don't think you need type castin here
    this.record = params.record
  }

  getNumber(k: K): number {
    return this.record[k];
  }
}


// Correctly inferred to be X<'a', 'b'>
const x1 = new X({ record: { 'a': 1, 'b': 2 } });
const result = x1.getNumber('a') // number

const x2 = new X<never>({}); // error


const x3 = new X<'c' | 'd'>({}); // error