JavaScript:合并多个类

JavaScript: Merge multiple classes

我想合并多个 classes 以实现后端和前端之间的可重用性和一致性。类似于:

import {
  IsEmail,
  IsString,
  MaxLength,
  MinLength,
  validateSync
} from "class-validator";

class UserUsername {
  @IsString()
  @MinLength(3)
  @MaxLength(10)
  username!: string;
}

class UserEmail {
  @IsEmail()
  email!: string;
}

class UserPassword {
  @IsString()
  @MinLength(8)
  password!: string;
}

class UserSecret {
  @IsString()
  secret!: string;
}

class User /* extends UserEmail, UserUsername, UserSecret */ {}
class UserDto /* extends UserEmail, UserUsername, UserPassword */ {}

const userDto = new UserDto();

userDto.username = "noerror";
userDto.email = "error";
userDto.password = "error";

console.log(validateSync(userDto).toString());

有类似的东西吗?

注意: 我的意思并不是像 TypeScript 那样的类型 &。主要目的是重用class验证。

Link to sandbox

问题:

export class User {
  @IsUUID()
  id!: string;

  @IsNotEmpty()
  @IsString()
  @MinLength(3)
  username!: string;

  @IsEmail()
  email!: string;

  @IsNotEmpty()
  @IsString()
  secret!: string;

  @IsNotEmpty()
  @IsAlpha()
  firstName!: string;

  @IsNotEmpty()
  @IsAlpha()
  lastName!: string;

  @IsBoolean()
  isEmailVerified!: boolean;
}

export class UserDto {
  @IsUUID()
  id!: string;

  @IsNotEmpty()
  @IsString()
  @MinLength(3)
  username!: string;

  @IsEmail()
  email!: string;

  secret?: never;

  @IsNotEmpty()
  @IsAlpha()
  firstName!: string;

  @IsNotEmpty()
  @IsAlpha()
  lastName!: string;

  @IsBoolean()
  isEmailVerified!: boolean;
}

export class SignUpUserDto {
  id?: never;

  @IsNotEmpty()
  @IsString()
  @MinLength(3)
  username!: string;

  @IsEmail()
  email!: string;

  secret?: never;

  @IsNotEmpty()
  @IsAlpha()
  firstName!: string;

  @IsNotEmpty()
  @IsAlpha()
  lastName!: string;

  isEmailVerified?: never;
}

export class UpdateUserDto {
  id?: never;

  @IsOptional()
  @IsNotEmpty()
  @IsString()
  @MinLength(3)
  username?: string;

  @IsOptional()
  @IsEmail()
  email?: string;

  secret?: never;

  @IsOptional()
  @IsNotEmpty()
  @IsAlpha()
  firstName?: string;

  @IsOptional()
  @IsNotEmpty()
  @IsAlpha()
  lastName?: string;

  isEmailVerified?: never;
}

export class SignInUserDto {
  @IsString()
  @IsNotEmpty()
  username!: string;

  @IsNotEmpty()
  @IsString()
  password!: string;
}

看到代码的重复程度了吗?

这样能达到你需要的吗?

Class User{
   userEmail: UserEmail;
   userUserName: UserUserName;
   userPassword: UserPassword;
}

我认为你的代码这样更有意义(并解决问题):

If you really need join three classes, you could try mixins: https://javascript.info/mixins


class UserLogin {
  @IsString()
  @MinLength(3)
  @MaxLength(10)
  username!: string;

  @IsEmail()
  email!: string;
}

class UserPassword {
  @IsString()
  @MinLength(8)
  password!: string;
}

class UserSecret {
  @IsString()
  secret!: string;
}

class User extends UserSecret {
   /// Secret things here...
}

class UserDto extends UserPassword {
   /// Password things here...
}

使用 mixin 模式有一种非常巧妙的方法。

class UserDto {}
interface UserDto extends UserEmail, UserUsername, UserPassword {}
applyMixins(UserDto, [UserEmail, UserUsername, UserPassword]);

const userDto = new UserDto();

userDto.username = 'noerror';
userDto.email = 'error';
userDto.password = 'error';

console.log(validateSync(userDto).toString());

使用此实用程序:

function applyMixins(derivedCtor: any, constructors: any[]) {
    constructors.forEach((baseCtor) => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
            Object.defineProperty(
                derivedCtor.prototype,
                name,
                Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
                    Object.create(null)
            );
        });
    });
}

感谢 Filip Kaštovský 向我指出了这个文档:How Does A Mixin Work?

我必须这样解决:

import { IsString, IsUUID, MinLength, validateSync } from 'class-validator';

type Constructor<T = {}> = new (...args: any[]) => T;

export function WithUserId<TBase extends Constructor>(Base: TBase) {
  class UserId extends Base {
    @IsUUID()
    id!: string;
  }

  return UserId;
}

export function WithUserUsername<TBase extends Constructor>(Base: TBase) {
  class UserUsername extends Base {
    @IsString()
    @MinLength(3)
    username!: string;
  }

  return UserUsername;
}

export function WithUserSecret<TBase extends Constructor>(Base: TBase) {
  class UserSecret extends Base {
    @IsString()
    secret!: string;
  }

  return UserSecret;
}

class User extends WithUserId(WithUserUsername(WithUserSecret(class {}))) {}

const user = new User();

user.id = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
user.username = "a";
user.secret = "a";

console.log(validateSync(user).toString());

Link to sandbox

编辑: 我克隆了 @nestjs/mapped-types and modified it to work in both the browser and nodejs. I wanted to publish it but I do not know a lot about publishing and whether it is actually going to work. It works fine in my set-up with Nx because I used Nx to create the library.