首页 > 解决方案 > 如何使用类属性中定义的泛型类型?

问题描述

我想创建自己的类控制器来处理快速请求。

我有的:

控制器:

import { Request, Response } from 'express';
import { transvalidate } from './schema/transform-validate';
import { ClassConstructor } from 'class-transformer';

export abstract class Controller<QuerySchema extends object> {
  querySchema?: ClassConstructor<QuerySchema>;
  query?: QuerySchema;

  constructor(
    protected readonly req: Request,
    protected readonly res: Response,
  ) {
    if (this.querySchema) {
      this.query = transvalidate(this.querySchema, this.req.query);
    }
  }

  getQuery(): QuerySchema {
    if (!this.query) {
      throw new Error(
        `Can't get query. Probably the querySchema is not provided.`,
      );
    }
    return this.query;
  }

  abstract handle(): Promise<unknown>;
}

获取猫控制器:

import { Controller } from './controller';
import { GetCatsQuerySchema } from './schema/query.schema';

export class CatsController extends Controller<GetCatsQuerySchema> {
  querySchema = GetCatsQuerySchema;

  handle(): Promise<unknown> {
    // here this.getQuery() returns transformed to GetCatsQuerySchema and validated req.query object
    return Promise.resolve(undefined);
  }
}

GetCatsQuerySchema

import { IsInt, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';

export class GetCatsQuerySchema {
  @Type(() => Number)
  @IsInt()
  @IsOptional()
  age?: string;
}

transform-validate功能:

import 'reflect-metadata';
import { ClassConstructor, plainToClass } from 'class-transformer';
import { validateSync, ValidationError } from 'class-validator';

export const transvalidate = <T extends object>(
  targetClass: ClassConstructor<T>,
  rawData: unknown,
) => {
  const transformedValue = plainToClass(targetClass, rawData);
  const errors = validateSync(transformedValue);
  if (errors.length) {
    const errorMessages = getAllErrorMessages(errors);
    throw new Error(errorMessages[0]);
  }
  return transformedValue;
};

const getAllErrorMessages = (errors: ValidationError[]) => {
  const errorMessages: string[] = [];
  errors.map((error) => {
    if (error.constraints) {
      errorMessages.push(...Object.values(error.constraints));
    }
  });
  return errorMessages;
};

但我不喜欢每个类QuerySchema在扩展控制器时都应该提供:

class CatsController extends Controller<GetCatsQuerySchema> {

而不是这个,我想找到一种在类属性中定义 QuerySchema 的方法:

export class CatsController extends Controller {
  querySchema = GetCatsQuerySchema;

可能吗?

标签: typescripttypescript-typingstypescript-generics

解决方案


如果该类是泛型的,则必须显式指定类型参数。这是我肯定会采用的方法。

现在,如果该类不是泛型的,则不必指定泛型类型参数。唯一的问题是您需要QuerySchema找到一种方法来代替说“使用派生类中的任何类型”。幸运的是 typescript 支持 polymorphic this,这基本上意味着当前类型是什么。我们可以使用多态this类型来提取querySchema派生类型中的实际类型:

type QuerySchema<T extends {querySchema?: ClassConstructor<object>}> = 
    InstanceType<Exclude<T['querySchema'], undefined>>
    
export abstract class Controller {
  querySchema?: ClassConstructor<object>;
  query?: QuerySchema<this>;

  constructor(
    protected readonly req: Request,
    protected readonly res: Response,
  ) {
    if (this.querySchema) {
      this.query = null!;
    }
  }

  getQuery(): QuerySchema<this> {
    // ...
  }

  abstract handle(): Promise<unknown>;
}

export class CatsController extends Controller {
  querySchema = GetCatsQuerySchema;

  handle(): Promise<unknown> {
    this.getQuery().age
    this.getQuery().age1 // error
    return Promise.resolve(undefined);
  }
}


export class GetCatsQuerySchema {
  age?: string;
}

游乐场链接

这似乎可行,但是由于条件类型,您迟早会遇到可分配性问题,因此我会坚持使用通用方法。所以使用风险自负。


推荐阅读