鉴于此代码,
interface TaskStartedEvent {
type: "started",
task: string
}
interface TaskLogEvent {
type: "log",
task: string,
message: string
}
interface TaskFailedEvent {
type: "failed",
task: string,
error?: string
}
interface FreeLog {
message: string | Error,
meta?: unknown
}
interface UndefinedTask {
task?: undefined
}
type TaskEvent = TaskStartedEvent | TaskLogEvent | TaskFailedEvent;
type RuntimeEvent = (FreeLog & UndefinedTask) | TaskEvent;
function foo(ev: RuntimeEvent) {
console.log(ev);
}
foo({ message: "bar", type: "log" });
Run Code Online (Sandbox Code Playgroud)
为什么 Typescript 编译器在这里没有失败?
我传递了一个type字段,所以它不能是一个(FreeLog & UndefinedTask)类型,但我没有传递一个字段,所以它也task不能是一个。TaskEvent
此代码编译时没有错误(typescriptlang.org链接)。
问题出在这一行:(FreeLog & UndefinedTask)。上面的交集产生这种类型:
type Debug<T> = {
[Prop in keyof T]: T[Prop]
}
// type Result = {
// message: string | Error;
// meta?: unknown;
// task?: undefined;
// }
type Result = Debug<FreeLog & UndefinedTask>
Run Code Online (Sandbox Code Playgroud)
我们最终得到了一个必需的属性:message。我们来测试一下:
function foo(ev: RuntimeEvent) {
console.log(ev);
}
foo({ message: '2' });
Run Code Online (Sandbox Code Playgroud)
但为什么还允许呢type? { message: string, type: 'log' }不是任何联合的类型。
type Check<T> = T extends RuntimeEvent ? true : false
// true
type Result = Check<{ message: 'string', type: 'log' }>
Run Code Online (Sandbox Code Playgroud)
{ message: 'string', type: 'log' }扩展是RuntimeEvent因为FreeLog & UndefinedTask它是它的一部分,并且它期望至少有一个属性message满足最低要求。
我们知道为什么允许这样做,但是呢type?为什么只允许使用log值?因为当你开始输入type属性时,TS 就开始检查你的参数。看来只有 union withlog具有message和type属性。
您可以提供也可以不提供task财产。它是由你决定。TS 没有抱怨,因为从技术上来说,你的论点符合要求。
为了使其按照您期望的方式工作,您可以使用StrictUnion助手:
// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
Run Code Online (Sandbox Code Playgroud)
完整示例:
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
interface TaskStartedEvent {
type: "started",
task: string
}
interface TaskLogEvent {
type: "log",
task: string,
message: string
}
interface TaskFailedEvent {
type: "failed",
task: string,
error?: string
}
interface FreeLog {
message: string | Error,
meta?: unknown
}
interface UndefinedTask {
task?: undefined
}
type TaskEvent = TaskStartedEvent | TaskLogEvent | TaskFailedEvent;
type RuntimeEvent = StrictUnion<(FreeLog & UndefinedTask) | TaskEvent>;
function foo(ev: RuntimeEvent) {
console.log(ev);
}
foo({ message: 'string', type: 'log' }); // expected error
Run Code Online (Sandbox Code Playgroud)
你可以在我的博客中找到更多有趣的例子
概括
FreeLog & UndefinedTask不期望您提供该task财产。至少task不是必需的,而TaskEvent需要task。所以,你最终遇到了联合中有两个元素的情况。一个元素需要task,而另一个则不需要。
..与受歧视工会的行为方式不一致...
请记住,您的工会不受歧视。在 中制作task所需的道具UndefinedTask。我会帮你的。
区分联合
如果您想使用区分联合,也标记联合,您应该type为联合的每个元素创建一个。type联合中每个元素的属性应该不同。参见示例:
interface TaskStartedEvent {
type: "started",
task: string
}
interface TaskLogEvent {
type: "log",
task: string,
message: string
}
interface TaskFailedEvent {
type: "failed",
task: string,
error?: string
}
interface FreeLog {
message: string | Error,
meta?: unknown
}
interface UndefinedTask {
task: undefined
}
type UndefinedEvent = (FreeLog & UndefinedTask) & {
type: 'undefined'
}
type TaskEvent = TaskStartedEvent | TaskLogEvent | TaskFailedEvent;
type RuntimeEvent = UndefinedEvent | TaskEvent;
function foo(ev: RuntimeEvent) {
console.log(ev);
}
foo({ message: "bar", type: "log" }); // error
Run Code Online (Sandbox Code Playgroud)
另外,请查看文档中的示例:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
Run Code Online (Sandbox Code Playgroud)
属性kind充当联合中每个元素的标签。例如,F# 还使用可区分联合:
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
Run Code Online (Sandbox Code Playgroud)
您可能已经注意到,Rectangle、Circle和Prism只是标签。
| 归档时间: |
|
| 查看次数: |
97 次 |
| 最近记录: |