Checked the optional key value object but still get 'xxx' is possibly 'undefined' in typescript

huangapple go评论73阅读模式
英文:

Checked the optional key value object but still get 'xxx' is possibly 'undefined' in typescript

问题

我正在翻译以下内容:

我正在传递一个可能具有可选键值的对象,例如: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})
英文:

I am passing an object which may have an optional ket value like: var1?: number; I used arrayEqual to make sure all key exists in the object before using it.

However, I still get 'input.var1' is possibly 'undefined'. in const var1 = input.var1 + 3.

How can I solve it? Or should I check it before passing it to foo() (e.g. use arrayEqual to check before passing to foo(), so I can remove all ? in the 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})

答案1

得分: 1

不要使用arraysEqual,因为它无法进行静态分析,你可以简单地检查input.var1input.var2是否未定义。

请尝试以下代码替代:

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

function foo(input: inputT): number {
  if (input.var1 === undefined || input.var2 === undefined) {
    return 0;
  }
  const var1 = input.var1 + 3;
  return var1;
}

foo({ var1: 2 });

使用这种方法,TypeScript 可以确保在访问input.var1时它是已定义的。

另外,这不是检查数组相等性的好方法。同时,避免使用双等号操作符(==),而是使用===

英文:

Instead of using arraysEqual, which cannot be statically analyzed, you can simply check if input.var1 or input.var2 is undefined.

Try this instead:

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

function foo(input: inputT): number {
  if (input.var1 === undefined || input.var2 === undefined) {
    return 0;
  }
  const var1 = input.var1 + 3;
  return var1;
}

foo({ var1: 2 });

With this approach, TypeScript can be sure that input.var1 is defined when you access it.

As a side note, this is not a good way to check for equality of arrays. Also, refrain from using the double equal operator (==). Use === instead.

答案2

得分: 1

TypeScript无法理解arraysEqual(Object.keys(input), ["var1", "var2"])input的表现类型有任何影响。TypeScript的缩小能力仅限于一组相当小的惯用编码技术,需要在编译器中明确实现,而该检查不在其中。一旦检查Object.keys(input)的结果,编译器就无能为力了,因为返回类型只是string[],与input无关。请参阅https://stackoverflow.com/q/55012174/2887218了解原因。


在这种情况下,你可以采取的一种方法是编写自己的自定义类型保护函数。你可以实现函数以进行所需的检查,并为函数提供一个调用签名,以表达检查将如何影响函数的一个参数。这基本上是一种让你告诉编译器如何缩小类型的方法,当它无法自己弄清楚时。

由于你想检查一个键数组中的所有值是否存在于一个对象中,我们可以将检查称为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>>;

这是一个泛型函数,接受一个泛型类型T的对象obj和一个泛型类型K[]的(扩展)数组keys,其中TK都是受限制的,以便K是已知可能存在于T中的所有键。返回值是obj is T & Required<Pick<T, K>>(使用RequiredPick实用类型),意味着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">>
   // 等同于 { 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 链接

英文:

TypeScript doesn't understand that arraysEqual(Object.keys(input), [&quot;var1&quot;, &quot;var2&quot;]) 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 https://stackoverflow.com/q/55012174/2887218 for why.


One approach you can take in situations like this is to write your own custom type guard function. You implement the function so it does the check you need, and give the function a call signature that expresses how the check will affect one of the parameters of the function. It's basically a way for you to tell the compiler how to narrow things, when it can't figure it out for itself.

Since you're trying to check whether all of the values in an array of keys are present in an object, we can call the check containsKeys, and give it a call signature like:

declare function containsKeys&lt;T extends Partial&lt;Record&lt;K, any&gt;&gt;, K extends keyof T&gt;(
    obj: T, ...keys: K[]
): obj is T &amp; Required&lt;Pick&lt;T, K&gt;&gt;;

That's a generic function which accepts an object obj of generic type T and a (spread) array keys of generic type K[], where T and K are constrained so that K are all keys which are known to (possibly) be in T. And the return value is obj is T &amp; Required&lt;Pick&lt;T, K&gt;&gt; (using both the Required and Pick utility types), meaning that a true return value implies that the type of obj can be narrowed from T to a version of T where all the properties with keys in K are known to be present.

It can be implemented however you like, although the compiler will just believe you that any boolean-returning code is acceptable. So we can take your code and just drop it in:

function containsKeys&lt;T extends Partial&lt;Record&lt;K, any&gt;&gt;, K extends keyof T&gt;(
    obj: T, ...keys: K[]): obj is T &amp; Required&lt;Pick&lt;T, K&gt;&gt; {
    return JSON.stringify(Object.keys(obj)) == JSON.stringify(keys);    
}

But that's not a good check. It relies on the order of the keys being the same in both cases, and that's not really something you can guarantee. Instead you should consider doing some order-independent check; perhaps:

function containsKeys&lt;T extends Partial&lt;Record&lt;K, any&gt;&gt;, K extends keyof T&gt;(
    obj: T, ...keys: K[]): obj is T &amp; Required&lt;Pick&lt;T, K&gt;&gt; {
    return keys.every(k =&gt; k in obj);
}

And now we can test it out:

function foo(input: Input): number {
    if (!containsKeys(input, &quot;var1&quot;, &quot;var2&quot;)) {
        return 0
    }
 
   // input: Input &amp; Required&lt;Pick&lt;Input, &quot;var1&quot; | &quot;var2&quot;&gt;&gt;
   // equivalent to { var1: number; var2: number; }

    const var1 = input.var1 + 3
    return var1
}

That works. After the initial block, input has been narrowed from Input to Input &amp; Required&lt;Pick&lt;Input, &quot;var1&quot; | &quot;var2&quot;&gt;&gt;, a complicated-looking type equivalent to {var1: number; var2: number}... that is, the same type as Input with the keys required instead of optional. So input.var1 is known to be a number and not number | undefined.

Playground link to code

huangapple
  • 本文由 发表于 2023年8月9日 12:57:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76864696.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定