在某些情况下,我有一个使用字符串,数字和布尔值的对象,以及一个使用正确类型返回值的吸气剂,例如像这样的东西:
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中缺少的功能/错误?
答案 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]
,这是您实际上想要作为kind
和value
允许的事物的并集。 (我本可以手动将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
)。
无论如何,希望能给您一些想法。祝你好运!