Lucian
Lucian

Reputation: 49

How to reslove TypeScript Interface heavy load?

type ItemProps = {
  a: string;
  b?: boolean;
  c?: string; 
}

const arr: ItemProps[] = [
 {
   a: 'demo',
   b: true,
   c: 'test'
 },
 {
   a: 'demo2'
 }
];

How to define ItemProps that if prop b existed. If prop b existed prop c must be defined?

I want this:

const arr: ItemProps[] = [
 {
   a: 'demo',
   b: true,
   c: 'test'
 },
 {
   a: 'demo2',
   b: true, // ERROR, because b exist,but c undefined
 }
];

Upvotes: 2

Views: 117

Answers (3)

Lucian
Lucian

Reputation: 49

Thanks, guys. I have get the answer!

type ExcludeKeys<T> = { [P in keyof T]: never; };

type RequireOrExcludeKeys<T, Keys extends keyof T = keyof T> = Pick<T,Exclude<keyof T, Keys>> & (Required<Pick<T, Keys>> | ExcludeKeys<Pick<T, Keys>>);

type ItemProps = RequireOrExcludeKeys<{
   a: string;
   b?: boolean;
   c?: string;
}, 'b' | 'c'>;

Playground

Upvotes: -1

Jonas Wilms
Jonas Wilms

Reputation: 138557

Use a union type:

type ItemProps = { a: string; } & ({} | { b: boolean;  c: string; });

With this Typescript already narrows down the union type with type guards, like e.g.:

let item: ItemProps = { a: "test", b: true };

if("b" in item) {
  type Narrowed = typeof item;
  console.log(item.c)
} else {
  type Narrowed = typeof item;
  console.log(item.c); // ERROR
}

However, unfortunately sometimes Typescript allows specifying more properties than the type actually has, when the type is a union type this seems to be the case. So although with a union type the typing is more accurate, during creation of new ItemProps it'll not help you.

For that you would need a discriminated union, e.g. if two different string literal types get used for the shared property a:

type ItemProps = { a: "without" } | { a: "with", b: boolean;  c: string; };

With that, when assigning an object literal, the union is discriminated to one of the object types based on a, and then only known properties can be specified:

type ItemProps = { a: "without" } | { a: "with", b: boolean;  c: string; };

const arr: ItemProps[] = [
 {
   a: 'with',
   b: true,
   c: 'test'
 },
 {
   a: 'without',
   b: true, // ERROR
 },
 {
   a: 'with', // ERROR
 }
];

Having such a discriminating property is very useful in many situations, e.g. in conditional logic, so you might want to add such a property to your data model.

if(item.a === "with") {
  console.log(item.b, item.c); // works, as item got narrowed down
}

Upvotes: 2

TeslaXba
TeslaXba

Reputation: 357

To the best of my knowledge, there is no way to do it like that with an interface. You can check it in your code before adding the data. An attribute can have multiple interfaces.

For example:

const someItem: InterfaceA | InterfaceB | undefined;

You can check then before updating the value with

if(b) 
{
  someItem = value structured as InterfaceA 
}
else {
  someItem = value structured as InterfaceB
}

You can take a look also on how to use classes and constructors https://www.typescriptlang.org/docs/handbook/classes.html.

Upvotes: 0

Related Questions