typescript - 检查了可选的键值对象,但仍然得到 'xxx' 可能是 typescript 中的 'undefined'

标签 typescript

我正在传递一个对象,该对象可能具有可选的 ket 值,例如:var1?: number ;我用过arrayEqual在使用之前确保对象中存在所有键。

但是,我仍然得到 'input.var1' is possibly 'undefined'.const var1 = input.var1 + 3 .

如何解决?或者我应该在将其传递给 foo() 之前检查它(例如,在传递到 arrayEqual 之前使用 foo() 进行检查,这样我就可以删除 ? 中的所有 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 无法理解 arraysEqual(Object.keys(input), ["var1", "var2"])input 的表观类型有任何影响。 narrowing TypeScript 的功能仅限于一小部分惯用的编码技术,这些技术需要在编译器中显式实现,而检查并不在其中。一旦您检查 Object.keys(input) 的结果编译器做任何事情都为时已晚,因为返回类型只是 string[]input 无关。请参阅Why doesn't Object.keys return a keyof type in TypeScript?为什么。


在这种情况下,您可以采取的一种方法是编写自己的 custom type guard function 。您实现该函数,以便它执行您需要的检查,并为该函数提供一个调用签名,以表达该检查将如何影响该函数的参数之一。这基本上是您在编译器无法自行解决问题时告诉编译器如何缩小范围的一种方法。

由于您试图检查键数组中的所有值是否都存在于对象中,因此我们可以调用检查 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接受对象的函数 obj通用类型 T和一个(扩展)数组 keys通用类型 K[] ,其中TKconstrained这样K是已知(可能)位于 T 中的所有键。返回值为obj is T & Required<Pick<T, K>> (同时使用 Required Pick 实用程序类型),这意味着 true返回值意味着 obj 的类型可以从 T 缩小范围到 T 的版本其中键为 K 的所有属性已知存在。

它可以按照你喜欢的方式实现,尽管编译器只会相信你任何 boolean -返回代码是可以接受的。因此我们可以将您的代码放入:

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);    
}

但这不是一个好的检查。它依赖于两种情况下键的顺序相同,而这并不是您真正可以保证的。相反,您应该考虑进行一些与顺序无关的检查;也许:

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
}

这有效。在初始 block 之后,input已缩小范围为InputInput & Required<Pick<Input, "var1" | "var2">> ,一个看起来复杂的类型,相当于 {var1: number; var2: number} ...即与 Input 相同的类型使用必需的键而不是可选的键。所以input.var1已知是 number而不是number | undefined .

Playground link to code

关于typescript - 检查了可选的键值对象,但仍然得到 'xxx' 可能是 typescript 中的 'undefined',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76864696/

相关文章:

javascript - 跨多个模块的 TypeScript 模块扩充

javascript - 第三个属性级别中的 Angular2 ngModel 绑定(bind)未定义

javascript - Angular D3 理解 attrTween 函数

javascript - JavaScript 过滤器的替代返回 - 如果未找到客户端则显示消息

angular - Angular 编译器要求 TypeScript >=2.7.2 和 <2.8.0,但找到了 2.8.3

javascript - Typescript 中的字段变量

typescript - 使用 Angular 6 的动态选项卡

typescript - 将接口(interface)列表注入(inject)类 - NestJs

javascript - 在抛出错误的情况下,如何自动有效地记录最后一个函数的参数(在 src 代码中)?

javascript - 无法在 JS 中将 toDateString() 应用于日期