英文:
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.var1
或input.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
,其中T
和K
都是受限制的,以便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">>
// 等同于 { 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
。
英文:
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 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<T extends Partial<Record<K, any>>, K extends keyof T>(
obj: T, ...keys: K[]
): obj is T & Required<Pick<T, K>>;
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 & Required<Pick<T, K>>
(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<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);
}
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<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);
}
And now we can test it out:
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
}
That works. After the initial block, input
has been narrowed from Input
to Input & Required<Pick<Input, "var1" | "var2">>
, 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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论