typescript - 如何让 TypeScript 类型保护对编译产生影响?
问题描述
我创建了一个名为的类KVMap
,它继承自Map
,但具有完全不同的类型。
class KVMap<T extends object> extends Map<keyof T, T[keyof T]> {
...
通常Map
s 仅在两种类型之间使用,例如Record
,但对象已经知道类型关联的关键,所以我实现了它,但在Map
. 例子:
type T = {
a: number,
b: bigint,
c: string
};
// becomes
type T = {
get(key: "a"): number | undefined,
set(key: "a", value: number): T,
get(key: "b"): bigint | undefined,
set(key: "b", value: bigint): T,
get(key: "c"): string | undefined,
set(key: "c", value: string): T,
};
所有类型都是可选undefined
的,因为可以调用.clear
或.delete
在键上。
到目前为止一切正常。
for 的原始类型存在一个问题Map
:调用.has
意味着您仍然可能会遇到编译器错误。例子:
if ( map.has(foo) ) {
map.get(foo)() // error: object may be undefined
}
这是由于.has
简单地返回一个布尔值。我试图解决这个问题。
我.has
在继承的类中重新定义如下:
has<K extends keyof T>(key: K): this is this & {
get(key: K): T[K]
};
这应该意味着.get
之后调用将不再具有| undefined
它的类型。
然而,我认为 的原始类型this
,因为它包含可选的 undefined,优先于类型保护联合。
有谁知道我该如何解决这个问题?
(如果有必要,我可以附上更多代码)
解决方案
从手册文档中并不清楚,但是当您创建两个可调用类型的交集时,它产生的类型就像一个具有两个调用签名的重载函数,其顺序与它们出现在交集中的顺序相同。因此,虽然直观的交集应该是可交换的,并且在 TypeScript 中它们通常是,但函数类型的交集不是:
type FN = {foo(): number};
type FS = {foo(): string};
declare const ns: FN & FS;
ns.foo().toFixed(); // okay
ns.foo().toUpperCase(); // error
declare const sn: FS & FN;
sn.foo().toFixed(); // error
sn.foo().toUpperCase(); // okay
ns.foo()
返回 a number
,而sn.foo()
返回 a string
,因为在这两种情况下,第一个调用签名都隐藏了第二个调用签名。
所以你的类型保护的问题this is this & { get(key: K): T[K] }
是原来的this
有一个get()
type 的方法Map<keyof T, T[keyof T]>["get"]
,所以产生的交集只是在这之后添加了一个新的重载方法。由于原始get()
方法适用于所有keyof T
输入,它将始终被选中,因此您添加的重载方法完全隐藏:
interface Tee {
a: string,
b: number,
c: boolean
}
const v = new KVMap<Tee>();
if (v.has("a")) {
v.get; /* OVERLOADED:
get(key: "a" | "b" | "c"): string | number | boolean | undefined;
get(key: "a"): string
*/
v.get("a").toUpperCase(); // error!
处理这个问题的最简单方法可能是改变你的交叉点的顺序,以便this
最后一个。所以每次你用 保护时has()
,true
结果应该将新的get()
调用签名添加到重载列表的开头,因此它应该优先:
class KVMap<T extends object> extends Map<keyof T, T[keyof T]> {
declare readonly has: <K extends keyof T>(key: K) => this is {
get(key: K): T[K]
} & this;
}
const v = new KVMap<Tee>();
if (v.has("a")) {
v.get("a").toUpperCase(); // okay now
}
万岁。
但是请注意,只有在将单个字符串文字类型的值传递给has()
. 如果参数 tohas()
是联合类型,那么您将度过一段糟糕的时光,undefined
尽管编译器向开发人员保证这是不可能的,但您仍然可能在运行时结束:
// problem
const bc = Math.random() < 0.5 ? "b" : "c";
const cb = (bc === "b") ? "c" : "b";
if (v.has(bc)) {
v.get(cb).toString(); // compiles fine, but error at runtime!
}
所以要小心。
推荐阅读
- flutter - Flutter:Pub Get 或其他东西完全破坏了我的应用程序并且恢复不起作用
- docker - Rocketchat 安装向导挂起
- azure - 来自 AAD 保护网站的 UWP 侧加载应用程序分发
- javascript - 如何使用 knockout.js 订阅变量状态更改
- excel - 如何将文本从一个 word 文档拉到另一个?
- corda - Corda Enterprise - 找不到 com.r3.libs:r3-libs-obfuscator:1.0
- c# - 如何在switch语句中使用字符串资源
- sql - SQL Server:连接来自两个不同数据库的两个表时出错
- r - 如何将 matlines 绘图应用于数据框列表
- iis - Blazor 服务器端 IIS 托管问题