如何将扩展类型保护添加到条件类型?或同等产品



假设我有一个带有泛型参数的类。它在类的某些属性上具有条件类型。这些条件类型取决于作为两个可能枚举值之一的泛型参数。在构造函数中,我传递一个与泛型参数遵循相同类型边界的类型。我所期望的是检查login_type === TwoChoices.REGISTER来缩小类的泛型参数T的类型,但这并没有发生。这是一个例子。

enum TwoChoices {
LOGIN,
REGISTER
}
class ConditionalGenericClass<T extends TwoChoices> {
password: string;
email: string;
username?: T extends TwoChoices.REGISTER ? string : never;
constructor(login_type: T) {
this.password = '';
this.email = '';
if (login_type === TwoChoices.REGISTER) {
this.username = '';
}
}
}

打字游戏场链接

上面抛出一个TypeError:Type '""' is not assignable to type '(T extends TwoChoices.REGISTER ? string : never) | undefined'.

我希望能够写的只是添加一个带有C extends TwoChoices.REGISTER的类型保护作为函数返回类型,如下所示:

enum TwoChoices {
LOGIN,
REGISTER
}
function typeGuardExtendsTwoChoicesRegister<C extends TwoChoices>
(login_type: C): C extends TwoChoices.REGISTER 
{
return login_type === TwoChoices.REGISTER;
}
class ConditionalGenericClass<T extends TwoChoices> {
password: string;
email: string;
username?: T extends TwoChoices.REGISTER ? string : never;
constructor(login_type: T) {
this.password = '';
this.email = '';
if (typeGuardExtendsTwoChoicesRegister(login_type)) {
this.username = '';
}
}
}

打字游戏场链接

然而,X extends Y似乎不是一个有效的类型保护返回类型。

静态键入这类JavaScript的动态类型场景的正确、更好的方法是什么?

你可能会建议使用这样的类继承:

class LoginCredentials {
password: string;
email: string;
constructor() {
this.password = '';
this.email = '';
}
}
class RegisterCredentials extends LoginCredentials {
username: string;
constructor() {
super();
this.username = '';
}
}

打字游戏场链接

但在我看来,仅仅为了编码类型而改变编码方式似乎与我对TypeScript的理解不符,TypeScript只是JavaScript的超集,你只需在超集中添加一些键入信息,但它需要你改变JavaScript的编码方式。当传输到≤es5时,使用类也会使行数加倍,尤其是当与类继承方法相比,在第一个代码块中使用const enums时。

这里发生的很多事情:

  • 您不希望使username成为可选的,因为当t是TwoChoices.REGISTER时需要它
  • 类型保护函数的正确语法是使用CCD_ 11来断言变量是检查类型的。这里不使用泛型,因为返回类型使用的是变量的名称而不是类型
function isRegister(login_type: TwoChoices): login_type is TwoChoices.REGISTER {
return login_type === TwoChoices.REGISTER;
}
  • T extends TwoChoices限制T并不意味着T只能是两个选择中的一个。就打字脚本而言,这两种方法都是有效的:
const myClass = new ConditionalGenericClass<TwoChoices.LOGIN | TwoChoices.REGISTER>(TwoChoices.LOGIN);
const myClass = new ConditionalGenericClass<TwoChoices>(TwoChoices.LOGIN);
  • 在构造函数中检查login_type的类型将缩小对login_type变量的理解,但它并没有缩小类的泛型t——这主要是因为前面的要点。如果T是TwoChoiceslogin_typeTwoChoices.REGISTER,则usernamenever,而不是像您所期望的那样是string,因为它基于T而不是login_type
  • 你是对的,我确实认为继承更好

可能的解决方案

考虑到当前设置的混乱性,我认为您必须断言usernameas的类型是正确的,因为typescript无法单独验证它。这似乎奏效了:

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 type to be never
const loginU = loginClass.username;
const registerClass = new ConditionalGenericClass(TwoChoices.REGISTER);
// expect type to be string
const registerU = registerClass.username;

打字游戏场链接