gpt4 book ai didi

typescript - 预计会出现错误,但代码编译正常

转载 作者:行者123 更新时间:2023-12-04 11:29:11 24 4
gpt4 key购买 nike

鉴于此代码,

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" });
为什么 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>
我们最终得到了一个必需的属性: message .让我们测试一下:
function foo(ev: RuntimeEvent) {
console.log(ev);
}

foo({ message: '2' });
但为什么它也允许 type ? { message: string, type: 'log' }不是任何联合的类型。
type Check<T> = T extends RuntimeEvent ? true : false

// true
type Result = Check<{ message: 'string', type: 'log' }>
{ message: 'string', type: 'log' }扩展 RuntimeEvent因为 FreeLog & UndefinedTask是它的一部分,它期望至少有一个属性 message以满足最低要求。
我们知道为什么允许这样做,但是 type 呢? ?为什么只允许使用 log值(value)?因为当你开始输入 type属性,TS 开始检查你的论点。看来只有与 log 联合两者都有 messagetype特性。
您可能会也可能不会提供 task属性(property)。它是由你决定。 TS 不会提示,因为从技术上讲,您的论点符合要求。
要使其以您期望的方式工作,您可以使用 StrictUnion helper :
// 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>
完整示例:
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

Playground
你可以在我的 blog 中找到更多有趣的例子。
摘要 FreeLog & UndefinedTask不希望您提供 task属性(property)。至少 task不是必需的,而 TaskEvent需要 task .所以,你最终遇到了一个联合中有两个元素的情况。一个元素需要 task另一个没有。

.. inconsistent with how a discriminated union would behave ...


请记住,您的工会不受歧视。制作 task所需 Prop UndefinedTask .我会帮你的。
歧视工会
如果您想使用 discriminated union , 还有 tagged unions ,您应该创建一个 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
另外,请查看文档中的示例:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
属性(property) kind用作联合中每个元素的标记。
例如,F# 也使用 discriminated unions :
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
您可能已经注意到, Rectangle , CirclePrism只是标签。

关于typescript - 预计会出现错误,但代码编译正常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69277990/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com