Охранники типа TS со сложным типом - PullRequest
3 голосов
/ 27 февраля 2020

Я столкнулся с интересным поведением охранников типа TS и хотел бы спросить вас, должен ли я просто принять это или я делаю что-то не так. Я собрал минимальный пример (ну, близкий к минимальному).

enum LegType {
  LegA = 'LegA',
  LegB = 'LegB',
}

interface LegA {
  type: LegType.LegA
  foo: string
}

interface LegB {
  type: LegType.LegB
  bar: string
}

type Leg = LegA | LegB

enum PlanType {
  T = 'T',
  C = 'C',
}

interface TPlan {
  type: PlanType.T
  legs: LegA[]
}

interface CPlan {
  type: PlanType.C
  legs: (LegA | LegB)[]
}

type Plan = TPlan | CPlan

const isLegA = (o: Leg): o is LegA => o.type === LegType.LegA

const getFoo = (plan: Plan): string | undefined => {
  // const legs: Leg[] = plan.legs // With this line the code builds with no errors.
  const legs = plan.legs // With this line the code contains error, the type of `legs` is resolved as: LegA[] | (LegA | LegB)[]
  const someLegA = legs.find(isLegA)
  return someLegA?.foo // Property 'foo' does not exist on type 'LegA | LegB'.
}

TS детская площадка

Почему необходимо добавить :Leg[]? Можно ли переписать код так, чтобы TS понимал, что это за правильный тип?

1 Ответ

4 голосов
/ 28 февраля 2020

Тьфу, я вижу, что происходит, и я думаю, что единственный разумный ответ здесь - сделать то, что ты сделал: аннотировать legs как Leg[].

Итак, подпись библиотечного типа для Array метод find() перегружен , а перегрузка, которая принимает определяемый пользователем тип защиты обратного вызова и возвращает тип суженного элемента, равна generi c:

find<S extends T>(
    predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any
  ): S | undefined;

find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;

Если вы не аннотируете legs, он получает тип объединения Array<Leg> | Array<LegA>. Этот тип считается совместимым с Array<Leg>, но он не будет обрабатывать его как таковой автоматически (что, вероятно, хорошо. Array<LegA> не должен позволять вам push() a LegB на него, но Array<Leg> должен) ,

Это означает, что тип legs.find сам по себе является объединением функций, подписи которых не идентичны:

type LegsFind = {
  <S extends LegA>(p: (this: void, v: LegA, i: number, o: LegA[]) => v is S, t?: any): S | undefined;
  (p: (v: LegA, i: number, o: LegA[]) => unknown, t?: any): LegA | undefined;
} | {
  <S extends Leg>(p: (this: void, v: Leg, i: number, o: Leg[]) => v is S, t?: any): S | undefined;
  (p: (v: Leg, i: number, o: Leg[]) => unknown, t?: any): Leg | undefined;
};

До TypeScript 3.3 компилятор просто сдаться и сказать вам, что он не знает, как вызвать объединение таких функций. И кто-то скажет вам изменить Array<X> | Array<Y> на Array<X | Y>, если вы вызываете немутантные методы, такие как find(), и вы будете использовать Leg[] и двигаться дальше.


TypeScript 3.3 введено улучшено поведение для вызова типов объединения . Есть случаи, когда компилятор может взять объединение функций и представить его как одну функцию, которая принимает пересечение параметров от каждого члена объединения. Вы можете прочитать больше об этом в соответствующем запросе на извлечение, microsoft / TypeScript # 29011 .

Но, бывают случаи, когда он не может сделать это : конкретно если несколько ветвей объединения перегружены или генерируют c функции. К сожалению для find(), это тот случай, когда вы находитесь. Похоже, что компилятор пытается найти способ сделать это вызываемым, но он отказывается от общих c подписей, соответствующих пользователю -defined-type-guard сужается и заканчивается только «обычным», вероятно потому, что они совместимы друг с другом:

type LegsFind33 = (p: (v: LegA, i: number, o: Leg[]) => unknown, thisArg?: any) => Leg | undefined;

Так что вы называете это успешно, сужения не происходит, и вы запускаете в проблему здесь. Проблема вызова союзов функций не полностью решена; его проблема GitHub microsoft / TypeScript # 7294 закрыта и помечена как «Revisit».

Таким образом, пока этот вопрос не будет вновь рассмотрен, советуем все же обрабатывать Array<X> | Array<Y> как Array<X | Y>, если вы вызываете немутирующие методы, такие как find() ... use Leg[] и двигаться дальше.


Хорошо, надеюсь, это поможет; удачи!

...