英文:
Checked the optional key value object but still get 'xxx' is possibly 'undefined' in typescript
问题
I am passing an object which may have an optional key value like: var1?: number
; I used arrayEqual
to make sure all keys exist 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});
英文:
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 的[缩小类型](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)能力仅限于一组相当小的习惯性编码技巧,需要在编译器中显式实现,而该检查并不在其中。一旦检查了 [`Object.keys(input)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) 的结果,编译器就无能为力了,因为返回类型只是 `string[]`,与 `input` 无关。参见 https://stackoverflow.com/q/55012174/2887218 了解更多信息。
在像这样的情况下,你可以采取一种方法,编写自己的[自定义类型保护函数](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)。你可以实现该函数以执行所需的检查,并为该函数提供一个调用签名,以表达检查将如何影响函数的一个参数。这基本上是一种方法,让你在编译器无法自行解决时告诉它如何缩小类型。
由于你试图检查键数组中的所有值是否存在于对象中,我们可以将检查称为 `containsKeys`,并为其提供一个调用签名,如下所示:
```typescript
declare function containsKeys<T extends Partial<Record<K, any>>, K extends keyof T>(
obj: T, ...keys: K[]
): obj is T & Required<Pick<T, K>>;
```typescript
这是一个[泛型](https://www.typescriptlang.org/docs/handbook/2/generics.html)函数,接受一个泛型类型 `T` 的对象 `obj` 和一个泛型类型 `K[]` 的(展开的)数组 `keys`,其中 `T` 和 `K` 都被[约束](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints),以便 `K` 是已知可能存在于 `T` 中的所有键。返回值是 `obj is T & Required<Pick<T, K>>`(同时使用了 [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) 和 [`Pick`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) 实用类型),这意味着 `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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论