聊天消息的TypeScript通用类型

问题描述 投票:0回答:2

如何基于另一个属性值约束属性的类型?

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'
  }
}
typescript generics
2个回答
1
投票

您可以创建discriminated unionTextMessageImageMessage类型。我将使用一个基本接口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别名代替interfaceTextMessageImageMessage更有意义,因为它们已经将其附加属性指定为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' } }

1
投票

这样的事情?

© www.soinside.com 2019 - 2024. All rights reserved.