如何基于另一个属性值约束属性的类型?
interface User {
name: string;
}
type Kind = 'text' | 'image';
interface TextMessage {
text: string;
}
interface ImageMessage {
src: string;
alt?: string;
}
type MessageType = TextMessage | ImageMessage
interface Message {
user: User;
kind: Kind;
// But, message of Kind 'text' must have a content of type TextMessage
content: MessageType;
}
我需要根据传递给同一对象的属性content
来约束type
键的形式。我知道我需要使用泛型,但我不知道该在哪里。
示例:
const textMsg: Message = {
user: {
name: 'bot',
},
kind: 'text',
content: {
// Not allowed, there is only allowed to have the `text` property
src: 'https://example.com/img.png'
}
}
const textMsg: Message = {
user: {
name: 'bot',
},
kind: 'image',
content: {
// Allowed, this is an ImageMessage
src: 'https://example.com/img.png'
}
}
您可以创建discriminated union或TextMessage
的ImageMessage
类型。我将使用一个基本接口AnyMessage
,它为所有扩展消息(包括类型kind
:]的约束)将Kind
字段指定为discriminant。
type Kind = 'text' | 'image'; interface AnyMessage { // This is where other common props such as 'user' go too kind: Kind; }
从此界面,您可以创建扩展的消息类型。
这样的区别联合的想法是用类型为kind: Kind
的任何字符串文字覆盖Kind
的类型约束,以将该名称与扩展名所具有的特定附加属性相关联:
interface TextMessage extends AnyMessage { kind: 'text'; // <- That's the TYPE 'text' text: string; } interface ImageMessage extends AnyMessage { kind: 'image'; src: string; alt?: string; } // This is the type to use for the actual messages: type Message = TextMessage | ImageMessage
对类型的快速测试表明您不再具有特定消息类型未知的属性:
// OK const m1: TextMessage = { kind: 'text', text: 'foo bar baz' }; // OK const m2: ImageMessage = { kind: 'image', src: 'foo bar baz' }; // Error: Type '{ kind: "text"; src: string; }' is not assignable to type 'TextMessage' const m3: TextMessage = { kind: 'text', src: 'foo bar baz' }; // Error: Type '{ kind: "image"; text: string; }' is not assignable to type 'ImageMessage' const m4: ImageMessage = { kind: 'image', text: 'foo bar baz' }
我忽略了您的消息具有
content
属性的事实,可以在其中放置消息数据的所有特定键。有多种方法可以做到,最简单的方法就是在每个扩展消息中指定content属性,例如:
interface ImageMessage extends AnyMessage { kind: 'image'; content: { src: string; alt?: string; } }
您还可以在
AnyMessage
基本界面中具有内容密钥,并且对于T
具有参数化类型Kind
,对于content属性具有参数化类型:U
:
interface AnyMessage<T extends Kind, U extends object = {}> { kind: T; content: U; }
现在传递给AnyMessage的类型将自动位于消息类型的
content
属性中。您可以按以下方式使用它-现在,使用type
别名代替interface
和TextMessage
的ImageMessage
更有意义,因为它们已经将其附加属性指定为U
:
type TextMessage = AnyMessage<'text', {
text: string;
}>
type ImageMessage = AnyMessage<'image', {
src: string;
alt?: string;
}>
type Message = TextMessage | ImageMessage
// OK
const m1: TextMessage = { kind: 'text', content: { text: 'foo bar baz' } };
// OK
const m2: ImageMessage = { kind: 'image', content: { src: 'foo bar baz' }};
// Error
const m3: TextMessage = { kind: 'text', content: { src: 'foo bar baz' } };
// Error
const m4: ImageMessage = { kind: 'image', content: { text: 'foo bar baz' } }
这样的事情?