ice*_*itz 5 interface unions typescript typeguards
我有一个函数,它接受一些参数并生成将传递到外部进程的对象。因为我无法控制最终需要创建的形状,所以我必须能够为我的函数采用一些不同的参数并将它们组装成适当的对象。这是一个非常基本的示例,展示了我遇到的问题:
interface T1A {
type: 'type1';
index: number;
}
interface T1B {
type: 'type1';
name: string;
}
interface T2A {
type: 'type2';
index: number;
}
interface T2B {
type: 'type2';
name: number;
}
function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
return {type, index: 3}
}
Run Code Online (Sandbox Code Playgroud)
这个特定的函数抱怨“type1”不能分配给“type2”。我读了一些有关类型保护的内容,并弄清楚了如何使这个简单的示例变得快乐:
function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
if (type === 'type1') {
return {type, index: 3}
} else {
return {type, index: 3}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,我不明白为什么这是必要的。类型检查对返回值的可能性完全没有任何作用。基于我的参数仅采用两个显式字符串之一的事实,单个 return 语句保证返回 T1A 或 T2A 之一。在我的实际用例中,我有更多类型和参数,但我分配它们的方式总是保证返回至少一个指定的接口。在处理接口时,联合似乎并不是真正的“这个或这个”。我试图分解我的代码来处理每种单独的类型,但是当有 8 种不同的可能性时,我最终会得到很多额外的 if/else 块,这些块看起来毫无用处。
我也尝试过使用类型type T1A = {...};
我是否误解了工会的某些内容,或者这是一个错误,或者是更简单的处理方法?
一般来说,TypeScript 不会执行从属性向上传播联合的类型。也就是说,虽然该类型的每个值都{foo: string | number}应该可分配给该类型{foo: string} | {foo: number},但编译器不会将这些值视为可相互分配:
declare let unionProp: { foo: string | number };
const unionTop: { foo: string } | { foo: number } = unionProp; // error!
Run Code Online (Sandbox Code Playgroud)
除了当您开始改变此类类型的属性时发生的奇怪事情之外,对于编译器来说,始终这样做的工作量太大,尤其是当您有多个联合属性时。microsoft/TypeScript#12052 中的相关评论说:
这种等价仅适用于具有单一属性的类型,并且在一般情况下并不成立。例如,考虑等于是不正确的,
{ x: "foo" | "bar", y: string | number }因为{ x: "foo", y: string } | { x: "bar", y: number }第一种形式允许所有四种组合,而第二种形式只允许两种特定的组合。
因此,这本身并不是一个错误,而是一个限制:编译器只会花费大量时间来处理联合来查看某些代码是否有效。
在 TypeScript 3.5 中,添加了支持,专门针对可区分联合的情况进行上述计算。如果您的联合体具有可用于区分联合体成员(这意味着单例类型,如字符串文字、数字文字、或)的属性,那么编译器将undefined按照null您的方式验证您的代码。想要它。
这就是为什么如果您更改T1A | T2A | T1B | T2B为 just ,它会突然起作用T1A | T2A,并且可以作为您问题的可能答案:
function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
const x: T1A | T2A = { type, index: 3 }; // okay
return x;
}
Run Code Online (Sandbox Code Playgroud)
后者是一个受歧视的工会:type财产会告诉你你有哪位成员。但前者则不然:type属性可以区分T1A | T1B和T2A | T2B,但没有属性可以进一步细分。不,仅仅类型中不存在index或存在并不能算作判别式,因为类型的值可能具有属性;TypeScript 中的类型并不精确。nameT1A name
上面的代码之所以有效,是因为编译器可以验证它的x类型T1A | T2A,然后可以验证它T1A | T2A是完整的子类型T1A | T2A | T1B | T2B。因此,如果您对两步流程感到满意,那么这对您来说是一个可能的解决方案。
如果你想T1A | T2A | T1B | T2B成为一个可区分的联合,你需要修改组成类型的定义,以便通过至少一个公共属性来真正区分。就像这样说:
interface T1A {
type: 'type1';
index: number;
name?: never;
}
interface T1B {
type: 'type1';
name: string;
index?: never;
}
interface T2A {
type: 'type2';
index: number;
name?: never;
}
interface T2B {
type: 'type2';
name: number;
index?: never;
}
Run Code Online (Sandbox Code Playgroud)
通过添加 type 的这些可选属性never,您现在可以使用type、name和作为 index判别式。和属性有时是,支持区分类型的单例类型name。然后这有效:indexundefined
function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
return { type, index: 3 }; // okay
}
Run Code Online (Sandbox Code Playgroud)
所以这是两个选择。在您知道某些东西是安全的但编译器不知道的情况下,始终可用的另一个选项是使用类型断言:
function hello2(type: 'type1' | 'type2') {
return { type, index: 3 } as T1A | T2A | T1B | T2B
}
Run Code Online (Sandbox Code Playgroud)
好的,希望有帮助;祝你好运!
| 归档时间: |
|
| 查看次数: |
2763 次 |
| 最近记录: |