I have this class definition:
export class Entity {
a?: string;
b?: string;
c?: string;
static required = ['c'] as const;
}
Basically I want to write a type definition which will take this type, and use the static array required
, to generate the type in which c
is not optional.
I wanted to do this:
export type WithRequired<T extends Entity> = Partial<T> & Required<Pick<T, typeof T.required[number]>>;
But this fails with the error:
'T' only refers to a type, but is being used as a value here.
If I slightly change the definition by replacing T.required
with Entity.required
, then it does not give me an error:
export type WithRequired<T extends Entity> = Partial<T> & Required<Pick<T, typeof Entity.required[number]>>;
Which works in this case.. but this is of course not a satisfactory solution, because I'd have to create a type for each variation of "Entity" in the project, instead of having 1 generic type to handle all cases.
So I actually have 2 questions, and having just 1 of them answered would help me a ton:
- Is there a way to "Require" properties of a class based on a static const array of that same class?
- Is there a way to change the part around
T.required
, so that typescript stops throwing errors at me?
CodePudding user response:
The type Entity
is the type of instances of your class. The value Entity
the constructor of the class, and typeof Entity
is the type of that constructor.
And the type of instances is completely disconnected from the type of its constructor. So if you have only the instance type, you cannot know the type of the constructor.
In fact, you can use a classes instance type as you would any interface, which is proof that the instance type has no idea about what constructed it.
class Foo { bar: string }
const foo: Foo = { bar: 'baz' } // works
However, you can go the other direction and derive the instance type from the constructor type with InstanceType<typeof MyClass>
.
That means that you must capture the constructor type as the generic, since that's where your static value actually is. You can then pull the required
static property form that type and merge it into the instance type.
export type WithRequired<T extends typeof Entity> =
Partial<InstanceType<T>> &
Required<Pick<InstanceType<T>, T['required'][number]>>;
And use like so:
type TestA = WithRequired<typeof Entity>
const testA1: TestA = { c: 'c'} // works
const testA2: TestA = { a: 'a' } // Property 'c' is missing in type '{ a: string; }' but required in type 'Required<Pick<Entity, "c">>'.(2322)
The downside is you have to pass in typeof MyClass
which is slightly more verbose. But if you want to access a static property then you need the type of the constructor, and there's no way you're going to get around that simple truth.