英文:
Why `map.has()` doesn't act as a type guard
问题
我面临了以下问题:在我的应用程序中,我有一个Map
,其中一组lambda函数与一些Role
配对。每个lambda函数都接受一个secondParameter
并返回一个settings
对象。
enum Role {
ROLE_1 = "role"
}
const settingsRole1 = {}
const defaultSettings = {}
const secondParameter = ""
const map = new Map<Role, (secondParameter: string) => any>([
[Role.ROLE_1, (secondParameter: string) => settingsRole1]
]);
const settings = map.get(Role.ROLE_1)(secondParameter) ?? defaultSettings;
在尝试调用从地图返回的函数时,我收到了以下错误:TS2722: Cannot invoke an object which is possibly 'undefined'
。我尝试像下面示例中所示对其进行了保护,但似乎不起作用...
const settings = map.has(Role.ROLE_1) ? map.get(Role.ROLE_1)(secondParameter) : defaultSettings;
正如这里回答的那样,似乎使用optional chaining operator
可以解决问题,因此以下代码片段实现了一个有效的保护:
const settings = map.get(Role.ROLE_1)?.(secondParameter) ?? defaultSettings;
为什么使用optional chaining operator
构建的保护有效,而使用if map.has()
构建的保护无效呢?
英文:
I faced the following problem: In my application I have a Map
in which a set of lambda functions are paired with some Role
s. Each lambda takes a secondParameter
and returns a settings
object.
enum Role {
ROLE_1 = "role"
}
const settingsRole1 = {}
const defaultSettings = {}
const secondParameter = ""
const map = new Map<Role, (secondParameter: string) => any>([
[Role.ROLE_1, (secondParameter: string) => settingsRole1]
]);
const settings = map.get(Role.ROLE_1)(secondParameter) ?? defaultSettings;
I received this error while trying to calling the function returning from the map: TS2722: Cannot invoke an object which is possibly 'undefined'
.
I tried to guard it as shown below, but it doesn't seem to work...
const settings = map.has(Role.ROLE_1) ? map.get(Role.ROLE_1)(secondParameter) : defaultSettings;
As answered here, it seems that using the optional chaining operator
works, so the following snippet implements a working guard
const settings = map.get(Role.ROLE_1)?.(secondParameter) ?? defaultSettings;
Why does the guard build using the optional chaining operator
work, while the one built with if map.has()
does not?
答案1
得分: 4
Map
上的 has()
方法 不充当 Map
实例的 类型守卫方法。因此,调用 map.has(x)
对 map
没有 缩小范围 的效果,因此不能影响对 map.get(x)
的后续调用。
在 microsoft/TypeScript#13086 上有一个开放的功能请求,以支持此功能,但尚未实现,因为至少对于可变的非 ReadonlyMap
(参见 https://stackoverflow.com/q/50046573/2887218 ),map.has(x)
现在为 true
并不意味着它将永远为 true
。正如TS 团队开发负责人在此评论中提到的:“这比看起来要复杂得多,因为您还必须定义 has
检查应该持续多长时间。”
要对此进行任何更改,需要在下面的两个 map.get()
行中正确运行:
const map = new Map([[1, { a: 1 }], [3, { a: 2 }]]);
if (map.has(2)) {
map.get(2).a; // 应该是可以的
map.delete(2);
map.get(2).a; // 不应该可以
}
现在它在这两个行上都会出错,这很烦人,但是安全的。
如果您愿意,您可以合并自己的 has()
类型守卫,也许像这样:
interface NarrowableMap<K, V> extends Map<K, V> {
has<P extends K>(k: P): this is { get(p: P): V } & this;
}
但是然后它将不会在这两个行上出错,这很方便,但是不安全,尤其是在第二行:
const map = new Map([[1, { a: 1 }], [3, { a: 2 }]]) as
NarrowableMap<number, { a: number }>;
if (map.has(2)) {
map.get(2).a; // 没有错误,这是好的
map.delete(2);
map.get(2).a; // 没有错误,这是不好的
}
目前的语言没有一种方法可以标记方法为重置控制流缩小。属性重新分配和 delete
关键字会自动触发此操作,但是映射操作不会触发此操作。
这就是为什么它不起作用的原因。
最简单的重构是放弃 has-then-get
,改为 get-then-validate
。所以你可以这样做:
const v = map.get(2);
if (v !== undefined) { v.a }
或者您可以使用可选链使其更简洁:
const w = v?.a
const x = map.get(2)?.a
英文:
The has()
method on Map
doesn't act as a type guard method on the Map
instance. So calling map.has(x)
has no narrowing effect on map
, and therefore cannot affect subsequent calls to map.get(x)
.
There is an open feature request at microsoft/TypeScript#13086 to support this, but it hasn't been implemented because, at least for a mutable, non-ReadonlyMap
(see https://stackoverflow.com/q/50046573/2887218 ), the fact that map.has(x)
is true
now doesn't mean it will be true
forever. As mentioned by the TS team dev lead in this comment: "This much, much, much, much more tricky than it looks because you also have to define how long the has
check should last."
Any change to accomodate this would need to act correctly in both map.get()
lines below:
const map = new Map([[1, { a: 1 }], [3, { a: 2 }]]);
if (map.has(2)) {
map.get(2).a; // should be okay
map.delete(2);
map.get(2).a; // should not be okay
}
Right now it errors on both of them, which is annoying, but safe.
You could merge in your own type guard for has()
if you want, maybe like this:
interface NarrowableMap<K, V> extends Map<K, V> {
has<P extends K>(k: P): this is { get(p: P): V } & this;
}
but then it would fail to error on both of them, which is convenient, but unsafe, especially on that second line.
const map = new Map([[1, { a: 1 }], [3, { a: 2 }]]) as
NarrowableMap<number, { a: number }>;
if (map.has(2)) {
map.get(2).a; // no error, that's good
map.delete(2);
map.get(2).a; // no error, that's bad
}
The language currently doesn't have a way to mark methods as resetting control flow narrowing. This happens automatically on property reassignment and the delete
keyword, but map operations don't trigger this.
So that's why it doesn't work.
The easiest refactoring is to give up on has
-then-get
and switch to get
-then-validate. So you can do it like this:
const v = map.get(2);
if (v !== undefined) { v.a }
Or you could use optional chaining to make it more concise:
const w = v?.a
const x = map.get(2)?.a
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论