Typescript无法从通用接口属性推断正确的类型

时间:2019-02-05 13:12:20

标签: typescript generics

在某些情况下,我有一个使用字符串,数字和布尔值的对象,以及一个使用正确类型返回值的吸气剂,例如像这样的东西:

interface AllowedMapTypings {
    'str': string;
    'lon': number;
    'str2': string;
}

const obj: AllowedMapTypings = {
    'str': 'foo',
    'lon': 123,
    'str2': 'foo'
};

function foo<T extends keyof AllowedMapTypings>(key: T): AllowedMapTypings[T] {
    return obj[key];
}

let str = foo('str'); // correctly inferred type 'string'

但是,如果我使用推断的接口类型作为参数,那么它将不起作用:

function fn<T extends keyof AllowedMapTypings>(key: string, kind: T, value: AllowedMapTypings[T]) {
    if (kind === 'str') {
        console.log(value.length); // Property 'length' does not exist on type 'AllowedMapTypings[T]'.
    }
}

看起来条件kind === 'str'不能满足要求,它不能正确地用作类型保护。 我是否缺少某些内容,或者这是TS中缺少的功能/错误?

1 个答案:

答案 0 :(得分:4)

这是known limitation。当您执行if (kind === 'str') {}时发生的控制流变窄不适用于通用类型参数或此类类型的值。我认为有人可能会争辩说if (kind === 'str') {}应该将kind的类型从T缩小到T & 'str',但是即使编译器为您做到了,它也不会缩小value。即使您知道kind的类型与value的类型相关,编译器也不会。

您随时可以通过自由使用type assertions来解决此问题。如果需要更多类型安全性,则可以使用一种变通方法,将泛型类型的值扩展为具体的并集,并将相关值打包到单个变量中,然后按期望的方式缩小范围。例如:

type KindValuePair<K extends keyof AllowedMapTypings = keyof AllowedMapTypings> =
  K extends any ? [K, AllowedMapTypings[K]] : never;

类型KindValuePair扩展为["str", string] | ["lon", number] | ["str2", string],这是您实际上想要作为kindvalue允许的事物的并集。 (我本可以手动将KindValuePair设置为该联合,但是我使用的是distributive conditional type来让编译器为我解决这个问题。)

然后您可以执行以下操作:

function fn<T extends keyof AllowedMapTypings>(key: string, kind: T, value: AllowedMapTypings[T]) {
  const kindValuePair = [kind, value] as KindValuePair; // assertion here
  if (kindValuePair[0] === 'str') {
    console.log(kindValuePair[1].length); // okay
  }
}

您断言[kind, value]的类型为KindValuePair,然后可以在kindValuePair上使用控制流范围缩小,以保持期望在检查{ {1}}。如果这对您有用,您甚至可以使用rest parameters使函数具体而非通用:

'str'

这完全避免了断言,并且就像我可以想象的那样是类型安全的。它还具有禁止这样的呼叫的副作用:

function fn(key: string, ...kindValuePair: KindValuePair) {
  if (kindValuePair[0] === 'str') {
    console.log(kindValuePair[1].length); // okay
  }
}

在通用版本中允许使用(将fn("", Math.random() < 0.5 ? 'str' : 'lon', 1); // error 指定为T)。

无论如何,希望能给您一些想法。祝你好运!