@soofftt91

Как правильно задать Types для объектов где есть несколько типиов «контента»?

У меня контент для поста хранится в виде JS объекта. Помимо разных titlt, description, и т.д. есть поле в котором хранится контент. Контент может быть разных типов: "text" | "video" | "header" | "code" | "images" | "remark" | "materials".

В данный момент у меня все реализовано примерно так:
export interface Post {
	title: string;
	content: [Content]
}
export interface Content {
	type: "text" | "video" | "header" | "code" | "images" | "remark" | "materials";
}
export interface ITextContent extends Content {
	value: string;
}
// ...


const items:Post = {
  title: "foo bar",
  content: [
    {type: "text", value: "lorem 1"},
    {type: "video", url: "lorem 2"},
    {type: "text", value: "lorem 3"}
    // ...
  ]
};

const onlyTextType:ITextContent[] = items.content.filter(({type}) => type === "text");
// ...


Когда я пытаюсь "уточнить" тип содержимого, получаю предупреждение от редактора:
Тип "Content[]" не может быть назначен для типа "ITextContent[]".
Свойство "value" отсутствует в типе "Content" и является обязательным в типе "ITextContent".ts(2322)
index.ts(31, 2): Здесь объявлен "value".


Подскажите пожалуйста как устранить ошибку? Есть подозрение, что я в принципе выбрал неправильный подход к "разметке типов" для контента, если так буду признателен за наставление на путь истины или хотя бы ссылку в каком направлении копать.
  • Вопрос задан
  • 155 просмотров
Решения вопроса 1
Aetae
@Aetae Куратор тега TypeScript
Тлен
export interface Post {
  title: string;
  content: Content[]; // мы ждём тут массив, а не кортеж из одного элемента
}

export type TContentType = "text" | "video" | "header" | "code" | "images" | "remark" | "materials";

// базовый интерфейс, чисто для надёжности, никуда не экспортируем
interface IContentBase {
  type: TContentType
}

export interface ITextContent extends IContentBase {
  type: "text";
  value: string;
}

export interface IVideoContent extends IContentBase {
  type: "video";
  url: string;
}

// ...

export interface IOtherContent extends IContentBase {
  type: "header" | "code" | "images" | "remark" | "materials";
}

// делаем union с type как discriminator field
export type Content = ITextContent |  IVideoContent | IOtherContent;

const items: Post = {
  title: "foo bar",
  content: [
    {type: "text", value: "lorem 1"},
    {type: "video", url: "lorem 2"},
    {type: "text", value: "lorem 3"}
    // ...
  ]
};

// используем тайпгард, тайпскрипт недостаточно умный, чтоб вывести Extract<Content, { type: "text" }> в таких случаях
const onlyTextType: ITextContent[] = items.content.filter((it): it is ITextContent => it.type === "text");
// ...
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@limpch
Ты пытаешься тип контент присвоить к типу iTextContent. В ITextContent у тебя только value, а ты пытаешься туда поместить type и value. Сделай так:

Поменяй тип у post.content с Content на ITextContent
export interface Content {
  type: "text" | "video" | "header" | "code" | "images" | "remark" | "materials";
}
export interface ITextContent extends Content {
  value: string;
}
export interface Post {
  title: string;
  content: ITextContent[]
}
// ...


const items:Post = {
  title: "foo bar",
  content: [
    {type: "text", value: "lorem 1"},
    {type: "video", url: "lorem 2"},
    {type: "text", value: "lorem 3"}
    // ...
  ]
};

const onlyTextType:ITextContent[] = items.content.filter(({type}) => type === "text");
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы