Reputation: 49
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
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'>;
Upvotes: -1
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
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