Вывод типа Typescript в операторе If не работает как переменная

#typescript

#typescript

Вопрос:

Рассмотрим следующий пример:

 class A {}
class B extends A {
    foo() {
        return 6;
    }
}

const variable: A = new B();

const isValid = variable instanceof B;

if (isValid) {
    variable.foo();
}
 

.foo() Вызов выдает следующую ошибку:

введите описание изображения здесь

Это имеет смысл, потому variable что имеет тип A . Но variable.foo() будет выполняться только, если variable является экземпляром B .

Проблема не возникает при выполнении этого таким образом:

 class A {}
class B extends A {
    foo() {
        return 6;
    }
}

const variable: A = new B();

if (variable instanceof B) {
    variable.foo();
}
 

Почему имеет значение, сохраняется ли условие в переменной, а не записывается явно в if ?

Комментарии:

1. instanceof действует как неявная защита типа, но компилятор не передает эту информацию через переменные. variable это просто логическое значение, у которого больше нет свойства сужения типа.

Ответ №1:

Обновление для TS4.4:

В TypeScript 4.4 будет введена поддержка анализа потока управления с псевдонимами. условия. Это означает, что иногда вы можете сохранить результаты проверки типов в const s и использовать их позже. Поэтому ваш приведенный выше код будет работать по желанию без каких-либо изменений.

Игровая площадка ссылка на код


Предварительный ответ TS4.4:

В microsoft / TypeScript # 12184 есть предложение (среди прочего, ссылки на них см. В Этом выпуске), позволяющее сохранять результаты защиты типа в логической переменной для последующего использования. Хотя большинство людей согласны с тем, что было бы неплохо иметь это, ведущий архитектор языка сказал:

Это потребовало бы от нас отслеживать, какие эффекты конкретное значение для одной переменной подразумевает для других переменных, что значительно усложнило бы (и связанное с этим снижение производительности) анализатор потока управления. Тем не менее, мы оставим это как предложение.


Чтобы немного расширить это, в настоящее время компилятор распознает, что внутри блока кода, который variable instanceof B был протестирован и оценен true , тип variable может быть сужен до B … но за пределами этой области компилятор может просто «забыть» о таком сужении:

 if (variable instanceof B) {
    variable.foo(); // okay
}
variable.foo(); // error
 

Для того, чтобы сработало что-то вроде следующего:

 const isValid = variable instanceof B;

if (isValid) {
  variable.foo(); 
}
 

компилятору не будет разрешено «забывать» сужение до isValid тех пор, пока он сам … или что-либо, от чего зависит isValid , не выйдет за рамки. На первый взгляд это может показаться разумным, но для каждого подобного случая, когда вам нужна такая память, кажется, что будет много случаев, когда дополнительная работа будет ненужной. Например, если у вас было это:

 const bar = Math.random() < 0.5 ? { a: 123, b: 456 } : undefined;
const baz = bar?.a;
 

Должен ли компилятор «помнить», что baz это будет number тогда и только тогда, когда bar это an {a: number, b: number} ? Возможно… но если когда-нибудь позже кто-то действительно не использует этот факт, как в:

 const qux = typeof baz === "number" ? bar.b : 789;
 

тогда отслеживание этого — просто потраченные впустую усилия.


Возможно, что в будущем кто-нибудь разработает способ сделать это лучше, чем в текущей ситуации, не делая анализ потока управления чрезмерно дорогим. Может быть, кто-то, кто хочет такого поведения для boolean переменной, мог бы вручную аннотировать ее чем-то вроде предиката типа, как упоминалось в этом комментарии к связанной проблеме?

 const isValid: variable is B = variable instanceof B; // not valid syntax now
 

Но на данный момент это не часть языка. Если вы действительно настроены решительно по этому поводу, вы можете обратиться к соответствующим проблемам GitHub и указать им свое 👍 или описать свой вариант использования и объяснить, почему текущее поведение использования не сохраненных типов недостаточно. Если этим занимается достаточное количество людей, это повышает вероятность того, что что-то в конечном итоге будет реализовано. Однако я бы на это не рассчитывал.


В обозримом будущем вам будет лучше просто использовать свои средства защиты типов немедленно, а не пытаться сохранить их на потом. Один из способов приблизиться к этому «отложенному» поведению — провести рефакторинг, чтобы то, что вы сохраняете, было более функциональным, чем boolean :

 const varIfValid = variable instanceof B ? variable : undefined;

if (varIfValid) {
  varIfValid.foo(); // okay
}
 

Это работает, потому varIfValid что is B | undefined , объединение, более непосредственно связанное с вызовом foo() , чем true | false is .


Игровая площадка ссылка на код