将数据字段与受歧视的联合链接,从而导致错误

时间:2017-05-02 17:11:56

标签: typescript typescript2.2

我正在使用Ionic(3.0.0)应用,并且经常要在数据interface中链接两个字段的类型。例如,NotificationDataverb: 'mention' | 'share' | ...reference: ProfileReference | DocumentReference | ...个字段,但实际上,NotificationData是一个联合类型:

{ verb: 'mention', reference: ProfileReference } | { verb: 'share', reference: DocumentReference }

到目前为止,这么好。还有其他字段不会随verb而改变,所以我通常创建一个基本接口,以不同的方式扩展它,然后采用联合,如下所示:

type X = 'a' | 'b' | 'c';
type Y = 'd' | 'e' | 'f';

interface I { x: X, other: Y };
interface A extends I { x: 'a', other: 'd' };
interface B extends I { x: 'b', other: 'e' };
interface C extends I { x: 'c', other: 'f' };
type J = A | B | C;

只要我对数据进行硬编码

就没问题
const j: J = { x: 'a', other: 'd' } // OK

或从switch

生成整个
function f(x: X): J {
  switch (x) {
    case 'a': return { x, other: 'd' };
    case 'b': return { x, other: 'e' };
    case 'c': return { x, other: 'f' };
    default: ((y: never) => {})(x);
  }
}
// OK

但如果我尝试以另一种方式生成它,那么Typescript会抱怨:

function other(x: X): Y {
  switch (x) {
    case 'a': return 'd';
    case 'b': return 'e';
    case 'c': return 'f';
    default: ((y: never) => {})(x);
  }
}

function g(x: X): J {
  return { x, other: other(x) }
}
// Error:
// Type '{ x: X; other: number; }' is not assignable to type Z.
//   Type '{ x: X; other: number; }' is not assignable to type 'C'.
//     Types of property 'x' are incompatible.
//       Type 'X' is not assignable to type '"c"'.
//         Type '"a"' is not assignable to type '"c"'.

事实上,即使没有数据字段的链接,这些错误也会出现:

interface I2 { x: X, other: any };
interface A2 extends I2 { x: 'a' };
interface B2 extends I2 { x: 'b' };
interface C2 extends I2 { x: 'c' };
type J2 = A2 | B2 | C2;

function h(x: X): J2 { return { x, other: 0 }; }
// Error:
// Type '{ x: X; other: number; }' is not assignable to type J2.
//   Type '{ x: X; other: number; }' is not assignable to type 'C2'.
//     Types of property 'x' are incompatible.
//       Type 'X' is not assignable to type '"c"'.
//         Type '"a"' is not assignable to type '"c"'.

我可以在我的类型签名中使用I

const xs: X[] = ['a', 'b', 'c'];
const is: I2[] = xs.map(x => ({ x, other: 0 })) // OK

但是这首先失去了我想要的领域的链接。我也可以像上面的switch一样使用function f,例如

const js: J[] = xs.map(f); // OK

但我希望能够在不创建单独功能的情况下执行此操作,例如

const j2s: J2[] = xs.map(x => ({ x, other: 0 }))
// Error: ... Type '"a"' is not assignable to type '"c"'.
无论如何,这感觉就像是Typescript应该能够表达/处理的东西。

所以我的问题是,是否有更好的方法在Typescript中表达此链接字段类型信息?或者从J[]以程序方式生成X[]的另一种方法?

1 个答案:

答案 0 :(得分:2)

这是TypeScript类型系统的限制。我会简化您问题中的代码,以便更轻松地发现问题:

function f(x: 'a' | 'b' | 'c'): { x: 'a' } | { x: 'b' } | { x: 'c' } {
    return { x: x };
}

// Type '{ x: "a" | "b" | "c"; }' is not assignable to type '{ x: "a"; } | { x: "b"; } | { x: "c"; }'.

此错误消息更容易理解。类型检查器会将您使用类型'a' | 'b' | 'c'的值作为对象中的x条目,并为表达式推断出{ x: 'a' | 'b' | 'c' }的类型。这与{ x: 'a' } | { x: 'b' } | { x: 'c' }的类型不同!特别是,类型检查器并不了解类型'a' | 'b' | 'c'的每个可能值确实产生了{ x: 'a' } | { x: 'b' } | { x: 'c' }类型的有效对象。

所以编译器需要一些帮助。在构造对象之前,我们可以通过执行案例分析来连接我们的鸭子:

function f(x: 'a' | 'b' | 'c'): { x: 'a' } | { x: 'b' } | { x: 'c' } {
    switch (x)
    {
        case 'a': return { x: x };
        case 'b': return { x: x };
        case 'c': return { x: x };
    }
}

case的每个分支中,x的类型变窄(分别为'a''b''c' ,因此返回的表达式分别为{ x: 'a' }{ x: 'b' }{ x: 'c' }类型。这些显然可以在{ x: 'a' } | { x: 'b' } | { x: 'c' }进行类型检查。

如果你真的无法承受switch声明所需的额外击键,我认为最简单和最实际的解决方法就是暂时关闭类型系统。

function f(x: 'a' | 'b' | 'c'): { x: 'a' } | { x: 'b' } | { x: 'c' } {
    return <any>{ x: x };
}

您可以合理地反对TypeScript 应该能够告诉{ x: 'a' | 'b' | 'c' }可以分配给{ x: 'a' } | { x: 'b' } | { x: 'c' }。像编译器编写器一样考虑这个问题:检查类似的类型的算法是什么样的?

  1. 将联合类型'a' | 'b' | 'c'拆分为其组成文字类型。
  2. 将结果类型{ x: 'a' } | { x: 'b' } | { x: 'c' }分解为其组成对象类型。
  3. 对于每对类型({x: x}resultType),请比较各种类型以确定它们是否可分配。
  4. 在伪代码中:

    foreach (t: Type in 'a' | 'b' | 'c')
    {
        var checks = false;
        foreach (u: Type in { x: 'a' } | { x: 'b' } | { x: 'c' })
        {
            if ({ x: t } :<= u)
            {
                checks = true;
            }
        }
        if (!checks)
        {
            return false;
        }
    }
    return true;
    

    我们编写了一个O(n ^ 2)类型检查算法!另一种方法可能是将类型放入产品总和的正常形式 - 这样您就可以比较{ x: { y: 'a' | 'b' } | { y: 'c' | 'd' } }{ x: { y: 'a' } } | { x: { y: 'b' } } | { x: { y: 'c' } } | { x: { y: 'd' } } - 但the normalisation algorithm blows up exponentially,而您仍然无法处理递归类型的同义词。

    故事的寓意是,在实践中,类型系统不仅仅是集合论。仅仅因为两种类型包含相同的值集并不意味着您应该期望它们被机器判断为相等。在最先进的类型系统 - 依赖类型系统中 - 类型检查器通常需要计算证明程序的健全性。 (例如,您可能知道数组的长度为n + m,但如果类型检查器需要一个长度为m + n的数组,则必须编写额外的代码来说服机器您# 39;已经履行了你的义务。)