What is a proper way in TypeScript to mark some attributes as required? Let's say we have certain object, that must contain id field, but can also have as many other fields as it wants:
interface SomeType {
id: number;
}
let obj: SomeType = { name: "OBJ" }; // GOOD ERROR! We must have `id`
let obj: SomeType = { id: 123 }; // OK
let obj: SomeType = { id: 123, name: "OBJ" }; // BAD ERROR! I just want to add a name
I do know that you can make optional attributes with a question mark, like this:
interface SomeType {
id: number;
name?: string;
engine?: string;
mother?: string;
}
But I don't want each time modify this interface by adding and mixing unrelated things to it.
Another option is of course to create a swarm of interfaces each extending the main one, but that sounds like a lot of lines of code:
interface SomeType {
id: number;
}
interface FirstType extends SomeType {
name: string;
}
At first I tried doing this:
interface SomeType extends any {
id: number;
}
But TypeScript can't find name any
. Maybe there is something like this with an exclamation point?
interface SomeType {
id!: number;
}
UPDATE
I am using this type of interface when creating Redux actions, which must have property type
and any number of other additional attributes. There are really a lot of actions, so I was reluctant to use any
as a type.
You can use an indexer for that:
interface SomeType {
id: number;
[key: string]: any;
}
let obj1: SomeType = { name: "OBJ" }; // GOOD ERROR! We must have `id`
let obj2: SomeType = { id: 123 }; // OK
let obj3: SomeType = { id: 123, name: "OBJ" }; // OK
[Playground]
However, please note that even the following code (i.e. with a non-string property key) is valid with the indexer above:
let obj4: SomeType = { id: 123, 1: "OBJ" }; // also OK
which confuses people but it is as it is.
But I don't want each time modify this interface by adding and mixing unrelated things to it.
Don't add to or mix unrelated things in the same interface. Create separate interfaces and then combine them when necessary by using intersection types.
interface FirstType {
id: number;
}
interface SecondType {
name: string;
}
let myIntersectionObj: FirstType & SecondType = {
id: 2,
name: "some string"
};
That's useful in situations like this:
function myFirstFunction(obj: FirstType) { /* does something */ }
function mySecondFunction(obj: SecondType) { /* does something */ }
function myFunction(obj: FirstType & SecondType) {
myFirstFunction(obj);
mySecondFunction(obj);
}
myFunction(myIntersectionObj);
Using intersection types helps to make sure everything is typed in the application, which will help prevent mistakes. Indexed types should ideally only be used for key value objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With