Home > Back-end >  infer type From another Propertyin Typescript?
infer type From another Propertyin Typescript?

Time:05-05

I have been bumping my head here and there and I couldn't gain any more progress than this

for example, I want TObject.props to accept 'href' or 'download' only if I pass TObject.name = 'a' and not If passed TObject.name = 'p'

and also how can I make reduxStoreType.objects accept an array of TObjects but not necessarily all of them should be one type like Tobjects<'a'>

for example I want the possibility to have the following

reduxStoreType.objects = [TObject<'a'>,TObject<'p'>,TObject<'div'>...]

here are my interfaces

interface TagsTypes {
  // HTML
  p: React.HTMLAttributes<HTMLParagraphElement>;
  a: React.AnchorHTMLAttributes<HTMLAnchorElement>;
  form: React.FormHTMLAttributes<HTMLFormElement>;
  head: React.HTMLAttributes<HTMLHeadElement>;
  img: React.ImgHTMLAttributes<HTMLImageElement>;
  input: React.InputHTMLAttributes<HTMLInputElement>;
  span: React.HTMLAttributes<HTMLSpanElement>;
  button: React.ButtonHTMLAttributes<HTMLButtonElement>;
  div: React.HTMLAttributes<HTMLDivElement>;
}

interface TObject<T extends keyof TagsTypes> {
  width: number;
  height: number;
  name:  T;
  props: TagsTypes[T];
  innerText: string;
}

interface CanvasType extends HTMLDivElement {}

interface reduxStoreType {
  canvas: CanvasType;
  objects: TObject<'a'>;
}

CodePudding user response:

Let's reduce your problem to something simpler, solve the simpler version, and then hopefully you can apply it to your real-world scenario.

We have some shapes:

type ShapeTypes = "circle" | "square" | "rectangle" | "triangle";

And we could express the objects like this (similar to your current TObject definition):

interface Shape<Type extends ShapeTypes> {
    type: Type;
    area: number;
}

But then here's the problem. What if we want a circle to have a radius or diameter? A square to have a side length? A triangle's classification?

Here's where we use a discriminated union:

type Shape =
    {
        type: "circle";
        area: number;
        radius: number;
    } | {
        type: "square";
        area: number;
        side: number;
    } | { ... }; // more

As you can see now, each type of shape has properties specific to them. Although this is a lot more typing it's what you want. You'll see when we use them like this:

if (shape.type === "circle") {
    shape.radius // number, no errors
    shape.side   // error! 
} else if (shape.type === "square") {
    shape.radius // error!
    shape.side   // number, no errors
} else if (...) { ... } // more if you want

And also using these in an array is as simple as Shape[], while still retaining all this type information.

If it's too long to type all the redundant shared properties, make a new base type and intersect it for each member in the union:

type Base = {
    area: number;
    // more properties if needed
};

type Shape = 
    {
        type: "circle";
        radius: number;
    } & Base | {
        // ...
    } | { ... }; // others

So now how do you apply this to your case?

Here's some code to get started:

type TBaseObject = {
    // ...
};

type TObject = {
    // ...
} | {
    // ...
}; // ...

interface reduxStoreType {
  canvas: CanvasType;
  objects: TObject[];
}
  • Related