There is a lot happening in this code:
- It is crucial to make the `username` field required and not optional when T is set to `TwoChoices.REGISTER`.
- The correct syntax for a type guard function involves using `is` to assert that the variable is of the checked type instead of using generics, as the return type should use the variable name rather than the type.
function isRegister(login_type: TwoChoices): login_type is TwoChoices.REGISTER {
return login_type === TwoChoices.REGISTER;
}
- Limiting T with `T extends TwoChoices` does not restrict T to only one of the two choices. Both scenarios shown below are valid in TypeScript:
const myClass = new ConditionalGenericClass<TwoChoices.LOGIN | TwoChoices.REGISTER>(TwoChoices.LOGIN);
const myClass = new ConditionalGenericClass<TwoChoices>(TwoChoices.LOGIN);
- While checking the type of `login_type` within the constructor helps understand the variable better, it doesn't narrow down the generic T of the class due to the ambiguity mentioned earlier. If T is `TwoChoices` and `login_type` is `TwoChoices.REGISTER`, then `username` becomes `never` instead of `string`, which might be unexpected.
- Inheritance may indeed be a better solution to consider in this case.
Possible Solution:
Given the complexity of the current approach, using `as` to assert the correct type for `username` seems necessary since TypeScript won't be able to validate it on its own. The following implementation seems to work:
type ConditionalUsername<T> = T extends TwoChoices.REGISTER ? string : never;
class ConditionalGenericClass<T extends TwoChoices> {
password: string;
email: string;
action: T;
username: ConditionalUsername<T>;
constructor(login_type: T) {
this.password = '';
this.email = '';
this.action = login_type;
this.username = ((isRegister(login_type)) ? '' : undefined) as ConditionalUsername<T>;
}
}
const loginClass = new ConditionalGenericClass(TwoChoices.LOGIN);
// Expect the type to be never
const loginU = loginClass.username;
const registerClass = new ConditionalGenericClass(TwoChoices.REGISTER);
// Expect the type to be string
const registerU = registerClass.username;
Typescript Playground Link