为什么没有打字稿允许使用整个精炼类型?

时间:2018-02-05 22:37:27

标签: typescript

我希望以下剪切编译,但它没有:

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

  • 这种行为是针对性的吗?如果是,为什么?
  • 这是一个错误吗?
  • 有解决方法吗?

1 个答案:

答案 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
}