Home > Software design >  How to properly define several types to one entity in typescript?
How to properly define several types to one entity in typescript?

Time:07-08

(code in the end)

I try to write section.full.link but it gives me such error:

Property 'link' does not exist on type 'SectionSingle | SectionTitle | SectionHeaderMedia'. Property 'link' does not exist on type 'SectionSingle'.ts(2339)

I cant understand what should i do to avoid this error. Sign ? afterfull property or something doesn't help

Code:

export type Section = {
    type: "single" | "double" | "title" | "headerMedia";
    full?: SectionSingle | SectionTitle | SectionHeaderMedia;
}


export type SectionSingle = {
    type: "text" | "media";
    content: any;
}

export type SectionTitle = {
    title: string;
}

export type SectionHeaderMedia = {
    link: string;
    alt: string;
}

var section: Section = {
    type: "headerMedia",
    full: {
        link: "/somelink",
        alt: "some alt text"
    }
}

const cond = section.type === "headerMedia" ? `${section.full.link}` : null

console.log(cond)

CodePudding user response:

You have to create type guard isSectionHeaderMedia

function isSectionHeaderMedia(section: unknown): section is SectionHeaderMedia {
    return _.isObject(section) && 'link' in section;
}

_ means lodash here.

isSectionHeaderMedia(section) && section.type === "headerMedia"
?
<div key={index} className="w-full">
    <Image
        src={section.full.link}
    />
</div>
:   null

CodePudding user response:

You can crate a generic Section that handles all the cases and generates proper full types.

Then you can create a union type of all the possible sections and then you can use it the way you want.

type HalfSection = {

}

type SectionSingleText = {

}

type SectionSingleMedia = {

}

type SectionTitle = {
    title: string;
}
type SectionHeaderMedia = {
    link: string;
    alt: string;
}

type SectionSingle = {
    type: "text" | "media"
    content: SectionSingleText | SectionSingleMedia
}

type SectionType = "single" | "double" | "title" | "headerMedia";

type SectionGeneric<ST extends SectionType> = {
    type: ST;
    full: ST extends "single" ? SectionSingle : ST extends "title" ? SectionTitle :  ST extends "headerMedia" ? SectionHeaderMedia : never;
    left?: HalfSection;
    right?: HalfSection;
}

type Section = SectionGeneric<"single"> | SectionGeneric<"double"> | SectionGeneric<"title"> | SectionGeneric<"headerMedia">

const a: Section = {
    type: "headerMedia",
    full: {
        link: "link",
        alt: "alt"
    }
};


const fToCall = (section: Section): void => {
    section.type === "headerMedia" && console.log(section.full.link)
}

fToCall(a)

Note:

  1. I had to declare some types to make this work
  2. You may just as well create a union type from the beginning.

Here's a playground.

You may also want to read up on Discriminated Unions.

CodePudding user response:

You can use a type assertion

const cond = section.type === "headerMedia"
  ? `${(section.full as SectionHeaderMedia).link}`
  : null

or if you want a runtime check, you can transform the SectionHeaderMedia type into a class, and add one more condition on the if using the instanceof operator

export class SectionHeaderMedia {
  link = '';
  alt = '';
}

section1.type === 'headerMedia' && section1.full instanceof SectionHeaderMedia
    ? section1.full.link
    : null;


CodePudding user response:

It seems like your current definition of Section is too loose, as it allows a value of a type like {type: "headerMedia", full: SectionSingle}, where the full property and the type property do not agree with each other. So you can't check section.type === "headerMedia" and conclude that section.full has a link property.

If you want to encode this constraint, you should make Section a discriminated union type, where type is the discriminant property. Here's one possibility (although I don't know the full extent of your requirements):

export type Section =
    { type: "single", full: SectionSingle } |
    { type: "title", full: SectionTitle } |
    { type: "headerMedia", full: SectionHeaderMedia };

So a Section is one of those three possibilities. If you check type and it is "headerMedia", then the full property has to be a SectionHeaderMedia. And thus your check will succeed as written with no error:

const cond = section.type === "headerMedia" ? `${section.full.link}` : null; // okay

Playground link to code

  • Related