我希望以下剪切编译,但它没有:
function f1(x: "A") {}
function f2(x: {member: "A"}) {}
let x: {member: "A" | "B"} = {member: "A"}
if (x.member == "A") {
f1(x.member) // compiles
f2(x) // doesn't compile
}
我想知道为什么会这样?
请注意,我正在使用strict
选项进行编译。
我的用例是我想为不可变类型编写一个类型安全更新程序函数,我发现它比我到目前为止看到的更符合人体工程学。
type Update1 = <Value,
K1 extends keyof Value>
(value: Value, k1: K1, replacement: Value[K1]) => Value;
type Update2 = <Value,
K1 extends keyof Value,
K2 extends keyof Value[K1]>
(value: Value, k1: K1, k2: K2, replacement: Value[K1][K2]) => Value;
type Update3 = <Value,
K1 extends keyof Value,
K2 extends keyof Value[K1],
K3 extends keyof Value[K1][K2]>
(value: Value, k1: K1, k2: K2, k3: K3, replacement: Value[K1][K2][K3]) => Value;
// ...
type Update = Update1 & Update2 & Update3; // ...
let update: Update = (...args: Array<any>) => {
throw "to be implemented..."
};
在许多情况下,此签名非常有用:
interface Data {
value1: number
value2: {
kind: "A",
aValue: number,
abValue: number
} | {
kind: "B",
bValue: number,
abValue: number
}
}
let data: Data = undefined as any // dummy value
data = update(data, "value1", 42) // compiles as expected
data = update(data, "value1", "42") // doesn't compile as expected
data = update(data, "notExistent", 42 as any) // dosen't compile as expected
data = update(data, "value2", "abValue", 42) // compiles as expected
但是当涉及缩小类型时它会起作用:
if (data.value2.kind == "A") {
data.value2.aValue // compiles, can access aValues
data = update(data, "value2", "aValue", 42) // doesn't compile, not expected!
}
我认为它不起作用,因为打字稿并没有将缩小的类型用于类型参数Value
。当我手动提供时,我无法将data
分配给它,就像在第一个示例中我无法使用f2
调用x
。
答案 0 :(得分:3)
您在此处使用的功能是tagged unions。从描述此功能的页面:
判别式属性类型guard是xp == v,xp === v,xp!= v或xp!== v形式的表达式,其中p和v是属性和字符串的表达式文字类型或字符串文字类型的联合。判别式属性类型保护将x的类型缩小为具有判别性属性p的x的那些组成类型,其具有v的可能值之一。
因此,在您的第一个示例中,问题是x
不是联合类型,因此没有组件可以将x
缩小到。然而,这将按预期工作:
let x: {member: "A" } | {member: "B"} = {member: "A"}
if (x.member == "A") {
f1(x.member) // compiles
f2(x) // works now !
}
将这个逻辑应用于更复杂的例子,数据必须是一个联合,以便有可能缩小到联盟的一个组成部分,所以人们可能希望这可以工作:
interface DataA {
value1: number
value2: {
kind: "A",
aValue: number,
abValue: number
}
}
interface DataB {
value1: number
value2: {
kind: "B",
bValue: number,
abValue: number
}
}
let data: DataA | DataB = undefined as any // dummy value
if (data.value2.kind == "A") {
data.value2.aValue // compiles, can access aValues
data = update(data, "value2", "aValue", 42) // doesn't compile, not expected!
}
上面的代码不起作用,对于嵌套的歧视联盟,这个开放的issue建议扩展类型缩小的方式以涵盖这一点。
唯一的解决方法是使用自定义类型后卫:
function isDataA(data: DataA | DataB) : data is DataA {
return data.value2.kind == "A";
}
if (isDataA(data)) {
data.value2.aValue // compiles, can access aValues
data = update(data, "value2", "aValue", 42) // compiles
}