Reputation: 13262
I'll often make a class with a constructor that can take any arbitrary members that are appropriate to the class:
class SomeClass {
propA: string;
propB: number;
constructor(obj: Partial<SomeClass>) {
Object.keys(obj).forEach(key => this[key] = obj[key]);
}
}
So then I can do new SomeObject({propA: 'whatever'})
or new SomeObject({propB: 3})
or any combination (obviously this is more useful when there are more properties.
(Assume this is a low-stakes personal project and we can ignore whatever reasons there might be for not writing a constructor like this.)
So, this is great, and it also means Intellisense will show me the options as I construct a new instance of the class.
However, that Intellisense list also includes the methods of the class (although my example doesn't have any methods, I think you get the idea).
Is there a variant or constraint for Partial<SomeClass>
that will only allow properties? And since I often use arrow functions, I guess they would be properties that aren't functions (since I think arrow functions are technically properties not methods).
Upvotes: 4
Views: 517
Reputation: 187034
You can use a mapped + conditional type get the keys that are not functions with something like:
type NonMethodKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T]
This goes through all object keys, and if it's a function, it sets the property type to never
, else whatever the key K
is. Then you index it by [keyof T]
which returns all values that are non never
. This gives you a union of property names that not functions.
Then you can simply Pick
those keys from a type.
type WithoutMethods<T> = Pick<T, NonMethodKeys<T>>
And then use it like so:
type A = WithoutMethods<{ a(): number, b: string}> // type: { b: string }
Or in your case:
constructor(obj: Partial<WithoutMethods<SomeClass>>) { /* ... */ }
In typescript 4.1 (unreleased, currently in beta), this gets a little easier with Key Remapping in Mapped Types and an as
clause of the mapped key. By mapping the key to never
, it's omitted.
type WithoutMethods<T> = {
[K in keyof T as
T[K] extends Function ? never : K
]: T[K]
}
type A = WithoutMethods<{ a(): number, b: string}> // type: { b: string }
Upvotes: 4