TypeScript doesn't understand that arraysEqual(Object.keys(input), ["var1", "var2"])
has any implication for the apparent type of input
. The narrowing abilities of TypeScript are confined to a fairly small set of idiomatic coding techniques that needed to be explicitly implemented in the compiler, and that check isn't among them. As soon as you check the results of Object.keys(input)
it's too late for the compiler to do anything, because the return type is just string[]
and has nothing to do with input
. See Why doesn't Object.keys return a keyof type in TypeScript? for why.
在这种情况下,你可以采取的一种方法是写下你自己的custom type guard function.您实现该函数,以便它执行所需的判断,并为该函数提供一个调用签名,表示该判断将如何影响该函数的一个参数.这基本上是一种让你tell编译器如何缩小范围的方法,当它不能自己找出它的时候.
由于您正在try 判断一个对象中是否存在键数组中的所有值,因此我们可以调用判断containsKeys
,并为其提供如下调用签名:
declare function containsKeys<T extends Partial<Record<K, any>>, K extends keyof T>(
obj: T, ...keys: K[]
): obj is T & Required<Pick<T, K>>;
这是一个generic函数,它接受泛型类型T
的对象obj
和泛型类型K[]
的(扩展)数组keys
,其中T
和K
是constrained,因此K
是已知(可能)在T
中的所有键.返回值是obj is T & Required<Pick<T, K>>
(使用Required
和Pick
两种实用程序类型),这意味着返回值true
意味着obj
的类型可以从T
缩小到T
的版本,其中K
中具有键的所有属性都是已知存在的.
您可以随心所欲地实现它,尽管编译器只会相信您可以接受任何返回boolean
的代码.这样我们就可以把你的代码放进go :
function containsKeys<T extends Partial<Record<K, any>>, K extends keyof T>(
obj: T, ...keys: K[]): obj is T & Required<Pick<T, K>> {
return JSON.stringify(Object.keys(obj)) == JSON.stringify(keys);
}
但这不是一张好支票.它依赖于order个密钥在这两种情况下都是相同的,这并不是您真正可以保证的.相反,您应该考虑做一些与订单无关的判断;也许:
function containsKeys<T extends Partial<Record<K, any>>, K extends keyof T>(
obj: T, ...keys: K[]): obj is T & Required<Pick<T, K>> {
return keys.every(k => k in obj);
}
现在我们可以测试一下了:
function foo(input: Input): number {
if (!containsKeys(input, "var1", "var2")) {
return 0
}
// input: Input & Required<Pick<Input, "var1" | "var2">>
// equivalent to { var1: number; var2: number; }
const var1 = input.var1 + 3
return var1
}
这很管用.在最初的区块之后,input
已经从Input
缩小到Input & Required<Pick<Input, "var1" | "var2">>
,这是一个看起来复杂的类型,相当于{var1: number; var2: number}
…也就是说,与Input
的类型相同,密钥是必需的,而不是可选的.因此,input.var1
被认为是number
,而不是number | undefined
.
Playground link to code个