Почему перегрузка функции устраняет проблему сужения типов? - PullRequest
0 голосов
/ 02 апреля 2020

Я столкнулся с проблемой, которая в основном заключается в сужении типа, возвращаемого объектом, доступ к которому осуществляется по его ключу. Допустим, у меня есть объект, значения которого могут быть number или string:

type Value = number | string;

enum DatumKey {
  numberDatum = 'numberDatum',
  stringDatum = 'stringDatum',
}

class DemoClass {
  private data = {
    [DatumKey.numberDatum]: 1337,
    [DatumKey.stringDatum]: 'foobar',
  }

  public getValue(key: DatumKey): Value {
    return this.data[key];
  }

  public doSomething(): void {
    const num: number = this.getValue(DatumKey.numberDatum); // TS expects `number | string`, I expect `number`
    const str: string = this.getValue(DatumKey.stringDatum); // TS expects `number | string`, I expect `string`

    console.log({ num, str });
  }
}

const dc = new DemoClass();
dc.doSomething();

См. Пример игровой площадки Typescript .


Failed подход

Итак, чтобы сузить тип, я попытался использовать обобщенный тип c, и тогда я могу сказать getValue<...>(), какой тип я ожидаю взамен:

public getValue<T extends Value>(key: DatumKey): T {
  // Error here: Type 'Value' not assignable to type 'T'
  return this.data[key];
}

public doSomething(): void {
  const num: number = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
  const str: string = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}

Однако при этом я получаю сообщение об ошибке TypeScript:

Type 'Value' is not assignable to type 'T'.
  'Value' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
    Type 'string' is not assignable to type 'T'.
      'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.

См. Пример игровой площадки Typescript .


Рабочий подход

Затем я натолкнулся на аналогичный вопрос, который предложил использовать перегрузку функций для решения проблемы . Оно делает. Однако я не уверен, почему перегрузка функции решает проблему:

public getValue<T extends Value>(key: DatumKey): T 
public getValue(key: DatumKey): Value {
  return this.data[key];
}

public doSomething(): void {
  const num = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
  const str = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}

См. Пример игровой площадки Typescript .

1 Ответ

0 голосов
/ 02 апреля 2020

Ваше рабочее решение на самом деле не работает. Переменные num и str имеют тип Value, а не number и string.

Более надежное решение:

enum DatumKey {
  numberDatum = 'numberDatum',
  stringDatum = 'stringDatum',
}

const data = {
  [DatumKey.numberDatum]: 1337,
  [DatumKey.stringDatum]: 'foobar',
};

function getValue<T extends keyof typeof data>(key: T): (typeof data)[T] {
  return data[key];
}

// You don't have to explicitly type num and str. They are automatically inferred 
const num = getValue(DatumKey.numberDatum);
const str = getValue(DatumKey.stringDatum);
...