我正在传递一个对象,该对象可能有一个可选的Ket值,如:var1?: number;在使用它之前,我使用arrayEqual来确保所有键都存在于该对象中.

然而,我仍然得到了const var1 = input.var1 + 3分中的'input.var1' is possibly 'undefined'.分.

我怎么才能解决它呢?或者我应该在传递到foo()之前判断它(例如,在传递到foo()之前使用arrayEqual进行判断,这样我就可以删除inputT中的所有?)?

interface inputT {
    var1?: number;
    var2?: number;
}

function arraysEqual(a1: string[], a2: string[]) {
    return JSON.stringify(a1) == JSON.stringify(a2);
  }

function foo(input: inputT): number {
    if (
        !arraysEqual(Object.keys(input), ["var1", "var2"])){
            return 0
        } 
    const var1 = input.var1 + 3
    return var1
}


foo({"var1":2})

推荐答案

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,其中TKconstrained,因此K是已知(可能)在T中的所有键.返回值是obj is T & Required<Pick<T, K>>(使用RequiredPick两种实用程序类型),这意味着返回值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

Typescript相关问答推荐

Typescript如何从字符串中提取多个文本

Angular 17 -如何在for循环内创建一些加载?

为什么ESLint抱怨通用对象类型?

为什么缩小封闭内部不适用于属性对象,但适用于声明的变量?

TypeScript Page Routing在单击时不呈现子页面(React Router v6)

在TypeScrip中分配回调时,参数的分配方向与回调本身相反.为什么会这样呢?

如何在typescript中设置对象分配时的键类型和值类型

我想创建一个只需要一个未定义属性的打字脚本类型

有条件地删除区分的联合类型中的属性的可选属性

类型脚本映射类型:从对象和字符串列表到键值对象

如何在Reaction Query Builder中添加其他字段?

Typescript 类型守卫一个类泛型?

TypeScrip-根据一个参数值验证另一个参数值

对有效枚举值的常量字符串进行常规判断

创建一个函数,该函数返回一个行为方式与传入函数相同的函数

为什么类型脚本使用接口来声明函数?他的目的是什么.

两个接口的TypeScript并集,其中一个是可选的

解析错误:DeprecationError: 'originalKeywordKind' 自 v5.0.0 起已被弃用,无法再使用

Typescript 编译错误:TS2339:类型string上不存在属性props

类型string不可分配给类型string| 联盟| 的'