首页 > 解决方案 > 打字稿:可以用任意类型实例化

问题描述

您好,我正在尝试使用 ddd,但是在使用 ts 对数据库进行操作后,我在建模映射器以返回我的域的实例时遇到问题:

这是我的域名:

export interface ILocationProps {
  stret_address: string;
  cep_code: CepCode;
}
export class LocationSeats extends AggregateRoot<ILocationProps> {
  get StretAddress(): string {
    return this.props.stret_address;
  }
  get CepCode(): CepCode {
    return this.props.cep_code;
  }
  private constructor(props: ILocationProps, id?: UniqueEntityID) {
    super(props, id);
  }
  public static create(
    props: ILocationProps,
    id?: UniqueEntityID,
  ): Result<LocationSeats> {
    const guardedProps = [
      { argument: props.cep_code, argumentName: 'cep_code' },
      { argument: props.stret_address, argumentName: 'stret_address' },
    ];

    const guardResult = Guard.againstNullOrUndefinedBulk(guardedProps);

    if (!guardResult.succeeded) {
      return Result.fail<LocationSeats>(guardResult.message);
    }

    const location = new LocationSeats(props, id);

    const idWasProvided = !!id;

    if (!idWasProvided) {
      location.when(new LocationCreatedEvent(location));
    }

    return Result.ok<LocationSeats>(location);
  }
}

这是我的映射器:

export default interface IMapperr<T> {
  toPersistence(t: any): Result<T>;
  toDomain(raw: any): Result<T>;
}
@singleton()
export class LocationMapper<Location = LocationSeats> implements IMapper<Location> {
  constructor() {}
  public toPersistence(t: any) {
    throw new Error('Method not implemented.');
  }
  private validate() {}
  public toDomain(raw: any): Result<Location> {
    const cepCodeorError = CepCode.create(raw.cep_code);
    const locationOrError = LocationSeats.create(
      {
        cep_code: cepCodeorError.getValue(),
        stret_address: raw.street_address,
      },
      new UniqueEntityID(raw.id),
    );
    return locationOrError;
  }
}

这是我的结果类:

export class Result<T> {
  public isSuccess: boolean;

  public isFailure: boolean;

  public error?: T | string;

  private _value?: T;

  constructor(isSuccess: boolean, error?: T | string, value?: T) {
    if (isSuccess && error) {
      throw new Error(
        'InvalidOperation: A result cannot be successful and contain an error',
      );
    }
    if (!isSuccess && !error) {
      throw new Error(
        'InvalidOperation: A failing result needs to contain an error message',
      );
    }

    this.isSuccess = isSuccess;
    this.isFailure = !isSuccess;
    this.error = error;
    this._value = value;

    Object.freeze(this);
  }

  getValue(): T {
    if (!this.isSuccess) {
      console.log(this.error);

      throw new Error(
        "Can't get the value of an error result. Use 'errorValue' instead.",
      );
    }
    if (!this._value)
      throw new Error(
        "Can't get the value of an error result. Use 'errorValue' instead.",
      );
    return this._value;
  }

  errorValue(): T {
    return this.error as T;
  }

  static ok<U>(value?: U): Result<U> {
    return new Result<U>(true, undefined, value);
  }

  static fail<U>(error: any): Result<U> {
    return new Result<U>(false, error);
  }

  static combine(results: Result<any>[]): Result<any> {
    for (const result of results) {
      if (result.isFailure) return result;
    }
    return Result.ok();
  }
}

但我收到了这个错误:

在我的 toDomain 函数上:

 public toDomain(raw: any): Result<Location> {
    const cepCodeorError = CepCode.create(raw.cep_code);
    const locationOrError = LocationSeats.create(
      {
        cep_code: cepCodeorError.getValue(),
        stret_address: raw.street_address,
      },
      new UniqueEntityID(raw.id),
    );
    return locationOrError;
  }

错误:

Type 'Result<LocationSeats>' is not assignable to type 'Result<Location>'.
  Type 'LocationSeats' is not assignable to type 'Location'.
    'Location' could be instantiated with an arbitrary type which could be unrelated to 'LocationSeats'.ts(2322)

我不知道为什么我的创建函数返回一个类型:结果

标签: typescript

解决方案


问题是您已标记toDomain为返回 aLocation这是一个没有约束的模板参数,但随后您将其硬编码为 return LocationSeats.create。Typescript 不知道是否LocationSeats.create与您可能选择传递的任何泛型类型兼容Location

设计对正在构建的类型通用的工厂函数的问题经常让刚接触 Typescript 的人感到困惑。原因通常是,与其他一些语言不同,泛型类型参数不能用作运行时对象,您可以在其上调用静态方法,例如您的create方法。它们在运行时被完全擦除。

在 Typescript 中,要创建通用工厂,您必须在运行时实际将构造函数引用传递给您的工厂。

像这样的东西可能接近你想要的(精简一下以显示我想要突出显示的部分):

class LocationSeats extends AggregateRoot<ILocationProps> {
  private constructor (props: ILocationProps, id?: UniqueEntityID) {
    super(props, id)
  }

  static create (props: ILocationProps, id?: UniqueEntityID): Result<LocationSeats> {
    return Result.ok(new LocationSeats(props, id))
  }
}

// We need an explicit factory interface for creating these objects
interface LocationCreator<T> {
  create (props: ILocationProps, id?: UniqueEntityID): Result<T>
}

class LocationMapper<Location> {
  // Need to accept the Location creator at runtime (Location is inferred)
  constructor (private creator: LocationCreator<Location>) {}

  toDomain (): Result<Location> {
    return this.creator.create({
        cep_code: '',
        stret_address: '',
      })
  }
}

// Need to pass the LocationSeats constructor in at runtime
const mapper = new LocationMapper(LocationSeats)

推荐阅读