Typescript 如果判别属性为union,则键入缩小错误

Typescript 如果判别属性为union,则键入缩小错误,typescript,Typescript,我用TypeScript编写了如下代码。 类型ABCD类似于状态对象,其kind属性表示状态的类型。因为它们中的一些(kind是'a'或'b')有一个共同的附加属性extra,所以我将它们的共同类型写为AB。其他的没有额外的属性,所以我将它们合并为CD,如下所示 type AB = { kind: 'a' | 'b', extra: string }; type CD = { kind: 'c' | 'd' }; type ABCD = AB | CD; function pr

我用TypeScript编写了如下代码。 类型
ABCD
类似于状态对象,其
kind
属性表示状态的类型。因为它们中的一些(
kind
'a'
'b'
)有一个共同的附加属性
extra
,所以我将它们的共同类型写为
AB
。其他的没有额外的属性,所以我将它们合并为
CD
,如下所示

type AB = {
  kind: 'a' | 'b',
  extra: string
};

type CD = {
  kind: 'c' | 'd'
};

type ABCD = AB | CD;

function printExtra(x: ABCD) {
  if (x.kind === 'c' || x.kind === 'd') return;
  console.log(x.extra);
}
如果
种类
既不是
'c'
也不是
'd'
,则函数
printExtra
将打印
extra
属性。 我确信编译会通过,因为if语句断言
x
不是
CD
,而且
AB
是该类型的唯一候选,显然
x
AB
。但是TypeScript编译器未能编译,并显示以下消息

类型“ABCD”上不存在属性“extra”。 类型“CD”上不存在属性“extra”

为什么上面的代码无法编译?我还尝试了以下代码,并确认它编译成功,但如果可能的话,我不想使用它,因为它在我的项目中有些冗余

type A = { kind: 'a', extra: string };
type B = { kind: 'b', extra: string };
type C = { kind: 'c' };
type D = { kind: 'd' };

type ABCD = A | B | C | D;

让你的逻辑更进一步:

type A = { kind: 'a', extra: string };
type B = { kind: 'b', extra: string };
type C = { kind: 'c' };
type D = { kind: 'd' };

type AB = A | B;
type CD = C | D;

type ABCD = AB | CD;
那么这也会起作用:

type AB = ({ kind: 'a' } | { kind: 'b' }) & { extra: string };
type CD = { kind: 'c' } | { kind: 'd' };

type ABCD = AB | CD;
让我们把这句话说得不那么冗长:

type Kind<T extends string> = { [K in T]: { kind: K } }[T];

type AB = Kind<'a' | 'b'> & { extra: string };
type CD = Kind<'c' | 'd'>;

type ABCD = AB | CD;
type-Kind={[K in T]:{Kind:K}}[T];
AB型=种类&{extra:string};
CD型=种类;
ABCD型=AB | CD;

您可以通过重构代码来检查其他方法

 function printExtra(x: ABCD) {
    if (x.kind === 'a' || x.kind === 'b') {
        console.log(x.extra); // no error
    }
  }

有其他方法可以做到这一点。你可以用

AB型={
种类:“a”|“b”,
额外:字符串
};
类型CD={
种类:“c”|“d”
};
ABCD型=AB | CD;
函数断言B(x:ABCD):断言x是AB{
如果(x.kind!='a'&&x.kind!='b'){
抛出新错误(“不是字符串!”);
}
}
函数printExtra(x:ABCD){
sab(x);
const result=x;//AB
console.log(x.extra);//确定
}
缺点:您应该处理错误

就我个人而言,我更喜欢第二种方法,使用typeguards。这样更安全:

AB型={
种类:“a”|“b”,
额外:字符串
};
类型CD={
种类:“c”|“d”
};
ABCD型=AB | CD;
常数isAB=(x:ABCD):x是AB=>x.kind=='a'| | x.kind=='b'
函数printExtra(x:ABCD){
如果(伊莎伯(x)){
const result=x.extra;//AB
}
}

通过定义函数isCd(x:ABCD):x是CD{return x.kind=='c'| | | x.kind=='d'},您总是可以给编译器一些帮助
。至于为什么会发生这种情况,我不知道。
类型ABCD
可以是更高的
AB
CD
。当
类型ABCD=AB
时,您可以使用
x.extra
,因为
AB
具有
extra
但当
类型ABCD=CD
时,您不能调用
x.extra
,因为
CD
不包括
extra