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

我正在开发一个Ionic(3.0.0)应用程序,并且经常想要在数据接口中链接两个字段的类型.例如,NotificationData有动词:’提及’| ‘分享’| …和参考:ProfileReference | DocumentReference | …字段,但实际上,NotificationData是一个联合类型:

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

到现在为止还挺好.还有其他字段不会随动词而改变,所以我通常创建一个基本接口,以不同的方式扩展它,然后使用union,如下所示:

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

或者从交换机生成整个

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"'.

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

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

但是这首先失去了我想要的领域的联系.我也可以像上面的函数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中表达这个链接字段类型信息?或者从X []程序生成J []的另一种方法?

最佳答案 这是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 };
    }
}

在案例的每个分支中,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’}.像编译器编写器一样考虑这个问题:检查类似的类型的算法是什么样的?

>分解联合类型’a’| ‘b’| ‘c’成为其组成的文字类型.
>分解结果类型{x:’a’} | {x:’b’} | {x:’c’}成为其组成对象类型.
>对于每对类型({x:x},resultType),比较类型以查看它们是否可分配.

在伪代码中:

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的数组,则必须编写额外的代码来说服机器已经履行了您的义务.)

点赞