Typescript根据参数为不同的类属性类型编写脚本

Typescript根据参数为不同的类属性类型编写脚本,typescript,class,generics,types,Typescript,Class,Generics,Types,我有一个User类,我用一个对象实例化它,如果用户没有经过身份验证,这个对象可能是null。我想根据其状态制作不同的属性类型。这就是我所尝试的: /`IUser`是一个具有诸如`id`、`email`等用户属性的界面。 类用户实现IUser{ id:T扩展空?空:字符串 电子邮件:T扩展空?空:字符串 年龄:T扩展空值?空值:数字 // ... 已验证:T扩展为null?false:true 构造函数(用户:T){ 如果(用户===null){ this.authenticated=false

我有一个
User
类,我用一个对象实例化它,如果用户没有经过身份验证,这个对象可能是
null
。我想根据其状态制作不同的属性类型。这就是我所尝试的:

/`IUser`是一个具有诸如`id`、`email`等用户属性的界面。
类用户实现IUser{
id:T扩展空?空:字符串
电子邮件:T扩展空?空:字符串
年龄:T扩展空值?空值:数字
// ...
已验证:T扩展为null?false:true
构造函数(用户:T){
如果(用户===null){
this.authenticated=false
this.id=this.email=this.age=null
}否则{
this.authenticated=true
;({
id:this.id,
电子邮件:this.email,
年龄:这个年龄,
}=用户)
}
}
}
但是此解决方案会导致错误
类型“true”不可分配给类型“T extends null”?false:true'
当设置
authenticated
字段时,在
else
子句中的'IUser | null'类型上不存在一系列错误,如
属性'email'。我的问题与以下方面有关:响应建议在何处执行
this.authenticated=true as any
。事实上,这确实消除了一个错误,但这个令人头痛的问题的关键是能够写

if(用户身份验证){
//电子邮件当然不是空的
const{email}=user
}

这不适用于此解决方案。

您似乎希望
用户
类型类似于
{authenticated:true,age:number,email:string,…}{authenticated:false,age:null,email:null,…}
。不幸的是,
接口
类型本身不能是联合体,因此没有简单的方法来表示这一点

您对条件类型的尝试可能仅在类实现之外起作用,并且仅当您的
用户
属于
用户|用户
类型,这将成为一个有区别的联合时才起作用。但是类型
User
并不等同于此。此外,它在类实现中不起作用,因为编译器没有对泛型类型进行控制流类型分析,所以类型参数
t
未指定;见和。因此,我们应该找到一种不同的方式来表示这一点


到目前为止,最直接的方法是让
用户
拥有一个可能的-
IUser
,而不是成为一个可能的-
IUser
。这种“拥有”而不是“存在”的原则被称为。您只需检查可能的--
IUser
类型的属性,而不必检查名为
unauthenticated
的其他属性。这是:

class User {
  constructor(public user: IUser | null) {}
}

const u = new User(Math.random() < 0.5 ? null : 
  { id: "alice", email: "alice@example.com", age: 30 });
if (u.user) {
  console.log(u.user.id.toUpperCase());
}
但这里额外的复杂性可能不值得这么做


如果您确实需要
User
类成为可能的-
IUser
,那么您还有其他选择。使用类获取联合类型的常规方法是使用继承;有一个抽象的
BaseUser
类,它捕获经过身份验证和未经身份验证的用户的共同行为,如下所示:

// the discriminated type from above, written out programmatically
type PossibleUser = (IUser & { authenticated: true }) |
  ({ [K in keyof IUser]: null } & { authenticated: false });

// a default unauthenticated user
const nobody: { [K in keyof IUser]: null } = { age: null, email: null, id: null };

class User {
  user: PossibleUser;
  constructor(user: IUser | null) {
    this.user = user ? { authenticated: true, ...user } : { authenticated: false, ...nobody };
  }
}

const u = new User(Math.random() < 0.5 ? null :
  { id: "alice", email: "alice@example.com", age: 30 });
if (u.user.authenticated) {
  console.log(u.user.id.toUpperCase());
}
type WideUser = { [K in keyof IUser]: IUser[K] | null };
abstract class BaseUser implements WideUser {
  id: string | null = null;
  email: string | null = null;
  age: number | null = null;
  abstract readonly authenticated: boolean;
  commonMethod() {

  }
}
然后创建两个子类,
AuthenticatedUser
UnauthenticatedUser
,专门化不同类型的行为:

class AuthenticatedUser extends BaseUser implements IUser {
  id: string;
  email: string;
  age: number;
  readonly authenticated = true;
  constructor(user: IUser) {
    super();
    ({
      id: this.id,
      email: this.email,
      age: this.age,
    } = user)
  }
}

type IUnauthenticatedUser = { [K in keyof IUser]: null };
class UnauthenticatedUser extends BaseUser implements IUnauthenticatedUser {
  id = null;
  email = null;
  age = null;
  readonly authenticated = false;
  constructor() {
    super();
  }
}
现在,您有两个类构造函数,而不是一个类构造函数,因此您的
用户
类型将是一个并集,要获得一个新的并集,您可以使用函数而不是类构造函数:

type User = AuthenticatedUser | UnauthenticatedUser;
function newUser(possibleUser: IUser | null): User {
  return (possibleUser ? new AuthenticatedUser(possibleUser) : new UnauthenticatedUser());
}
它具有预期的行为:

const u = newUser(Math.random() < 0.5 ? null : 
  { id: "alice", email: "alice@example.com", age: 30 });
if (u.authenticated) {
  console.log(u.id.toUpperCase());
}
请注意,我将其命名为
\u User
,以便名称
User
可用于以下类型和构造函数定义:

// discriminated union type, named PossibleUser before
type User = (IUser & { authenticated: true }) | 
  ({ [K in keyof IUser]: null } & { authenticated: false });
const User = _User as new (...args: ConstructorParameters<typeof _User>) => User;

好的,完成了。走哪条路取决于你。就我个人而言,我推荐最简单的解决方案,它需要一些重构,但却是最友好的类型脚本

// discriminated union type, named PossibleUser before
type User = (IUser & { authenticated: true }) | 
  ({ [K in keyof IUser]: null } & { authenticated: false });
const User = _User as new (...args: ConstructorParameters<typeof _User>) => User;
const u = new User(Math.random() < 0.5 ? null : 
  { id: "alice", email: "alice@example.com", age: 30 });
if (u.authenticated) {
  console.log(u.id.toUpperCase());
}