Типы для рекурсивной функции - PullRequest
3 голосов
/ 25 октября 2019

Я пытаюсь написать unwrap метод класса, который содержит значение, или его экземпляр с внутренним значением:

class Identity<T> {
  private readonly value: T;

  constructor(value: T) {
    this.value = value;
  }

  static of<T>(value: T): Identity<T> {
    return new Identity(value);
  }

  join(): T { // which return type should go here?
    if (this.value instanceof Identity) {
      return this.value.join();
    }

    return this.value;
  }
}

Вот тест:

const one = Identity.of(Identity.of(Identity.of(123)));
const result: number = one.join();
expect(result).toEqual(123);

Спасибо вам, ребята !!

Ответы [ 2 ]

2 голосов
/ 26 октября 2019

Вы можете изменить параметр типа T, чтобы он всегда был полностью вложенным типом:

class Identity<T> {
  private readonly value: T | Identity<T>;

  constructor(value: Identity<T>);
  constructor(value: T);

  constructor(value: any) {
    this.value = value;
  }

  static of<T>(value: Identity<T>): Identity<T>;
  static of<T>(valud: T): Identity<T>;
  static of<T>(value: T | Identity<T>) {
    return new Identity(value);
  }

  join(): T {
    if (this.value instanceof Identity) {
      return this.value.join();
    }
    return this.value;
  }
}

const one = Identity.of(Identity.of(Identity.of(123)));
const result: number = one.join();

const result2 = new Identity(new Identity(new Identity(321)));
// Identity<number>

Я бы предложил вышеописанный метод, так как он, по-видимому, обеспечивает ту функциональность, которая вам действительно нужна.

Если вам нужно, чтобы тип T действительно был вложенным Identity<Identity<...>>, то все еще возможно, если бы value был общедоступным, мы могли бы сделать следующее:

class Identity<T> {
  public readonly value: T;

  constructor(value: T) {
    this.value = value;
  }

  static of<T>(value: T): Identity<T> {
    return new Identity(value);
  }

  public join(): IdentityJoinResult<T> {
    if (this.value instanceof Identity) {
      return this.value.join();
    }

    return this.value as IdentityJoinResult<T>;
  }
}

type IdentityJoinResult<T> = 
  T extends Identity<any>
    ? { [Key in keyof T]: IdentityJoinResult<T[Key]> }["value"]
    : T
;

const one = Identity.of(Identity.of(Identity.of(123)));
const result: number = one.join();

const result2 = new Identity(new Identity(new Identity(321))).join();

Однако еслиизменив value на приватный, вы заметите, что вы больше не можете индексировать по «значению» в сигнатуре типа, поэтому последнее, что мы можем сделать, чтобы это исправить, - это перенести нашу логику типов на отдельный открытый интерфейс, которыймы можем определить по типу:

class Identity<T> {
  private readonly value: T;

  constructor(value: T) {
    this.value = value;
  }

  static of<T>(value: T): Identity<T> {
    return new Identity(value);
  }

  public join(): IdentityJoinResult<T> {
    if (this.value instanceof Identity) {
      return this.value.join();
    }

    return this.value as IdentityJoinResult<T>;
  }
}

interface NestedIdentity<T> {
  _value: T extends Identity<infer U> ? NestedIdentity<U> : T;
}

type NestedIdentityValue<T> = 
  T extends NestedIdentity<any>
    ? { [Key in keyof T]: NestedIdentityValue<T[Key]> }["_value"]
    : T
;

type IdentityJoinResult<T> = NestedIdentityValue<NestedIdentity<T>>;

const one = Identity.of(Identity.of(Identity.of(123)));
const result: number = one.join();
const result2 = new Identity(new Identity(new Identity(321))).join();
1 голос
/ 26 октября 2019

Как упоминал Инго в комментариях, infer сработало бы, если бы не было ограничения в TS.

class Identity<T> {
  private readonly value: T;

  constructor(value: T) {
    this.value = value;
  }

  static of<T>(value: T): Identity<T> {
    return new Identity(value);
  }

  join(): T extends Identity<infer U> ? U : T {
    if (this.value instanceof Identity) {
      return this.value.join() ;
    }

    return this.value as any;

    // return this.value as T;
  }
}

const one = Identity.of(Identity.of(Identity.of(123)));
const result = one.join();
expect(result).toEqual(123);
T extends Identity<infer U> ? U : T

здесь ключ.

...