英文:
Type guards for types with the same fields
问题
我遇到了结构类型的一个限制,想知道是否有绕过它的方法。
假设我有以下类型的层次结构:
type Message = HelloMessage | GoodbyeMessage
type HelloMessage = {
message: string
}
type GoodbyeMessage = {
message: string
}
并且我们定义了一些类型守卫如下:
function isHello(value: any): value is HelloMessage {
return !!value && !!value.message && typeof value.message === "string" && value.message.includes("hello")
}
function isGoodbye(value: any): value is GoodbyeMessage {
return !!value && !!value.message && typeof value.message === "string" && value.message includes("goodbye")
}
尝试在函数中使用这些类型守卫会导致编译器将后续使用的部分类型为 never
:
function someFunc(input: Message): string {
if (isHello(input)) {
return input.message
}
if (isGoodbye(input)) {
return input.message // 编译器在这里报错
}
return "unknown"
}
一些明显的解决方案是将输入类型标记为 any
或在 if
语句内将 input
强制转换为 GoodbyeMessage
,但这两种方法都不太优雅。
这是否只是 TypeScript 结构类型的限制,或者是否有一些其他方法可以让它按照我的期望工作?
英文:
I stumbled into a limitation of structural typing and wondered if there was a way round it.
Suppose I have a hierarchy of types as follows:
type Message = HelloMessage | GoodbyeMessage
type HelloMessage = {
message: string
}
type GoodbyeMessage = {
message: string
}
and we define some type guards as follows:
function isHello(value: any): value is HelloMessage {
return !!value && !!value.message && typeof value.message === "string" && value.message.includes("hello")
}
function isGoodbye(value: any): value is GoodbyeMessage {
return !!value && !!value.message && typeof value.message === "string" && value.message.includes("goodbye")
}
Attempting to use the type guards in a function leads to subsequent uses being typed as never
by the compiler:
function someFunc(input: Message): string {
if (isHello(input)) {
return input.message
}
if (isGoodbye(input)) {
return input.message // compiler errors here
}
return "unknown"
}
Some obvious solutions are to type the input as any
or cast input
as GoodbyeMessage
inside the if
statement, but neither of these feel particularly elegant.
Is this simply a limitation of Typescript's structural nature of type
or is there some other magic that can be incanted to make it work as I'm expecting?
答案1
得分: 1
你说得对,这是类型系统的一个限制。即使类型守卫起作用,你也无法真正确定一个函数是否传递了 HelloMessage
还是 GoodbyeMessage
,因为它只看到 { message: string }
。你可以改用标记联合类型,这种类型可以使用类型守卫进行区分:
type Message = HelloMessage | GoodbyeMessage;
type HelloMessage = {
type: "hello";
message: string;
}
type GoodbyeMessage = {
type: "goodbye";
message: string;
}
// 更倾向于使用 `unknown` 来提高类型安全性。
function isHello(value: unknown): value is HelloMessage {
return typeof value === "object"
&& value !== null
&& "type" in value
&& typeof value.type === "string"
&& value.type === "hello";
}
function isGoodbye(value: unknown): value is GoodbyeMessage {
return typeof value === "object"
&& value !== null
&& "type" in value
&& typeof value.type === "string"
&& value.type === "goodbye";
}
英文:
You're right, this is a limitation of the typing system. Even if the type guards worked, you couldn't actually tell if a function was passed HelloMessage
or GoodbyeMessage
, since all it sees is { message: string }
. You could instead use a tagged union type, which can be discriminated with type guards:
type Message = HelloMessage | GoodbyeMessage;
type HelloMessage = {
type: "hello";
message: string;
}
type GoodbyeMessage = {
type: "goodbye";
message: string;
}
// Prefer to use `unknown` for additional type safety.
function isHello(value: unknown): value is HelloMessage {
return typeof value === "object"
&& value !== null
&& "type" in value
&& typeof value.type === "string"
&& value.type === "hello";
}
function isGoodbye(value: unknown): value is GoodbyeMessage {
return typeof value === "object"
&& value !== null
&& "type" in value
&& typeof value.type === "string"
&& value.type === "goodbye";
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论