Проверка типов шаблонов¶
28.02.2022
Обзор проверки типов шаблонов¶
Подобно тому, как TypeScript выявляет ошибки типов в коде, Angular проверяет выражения и привязки в шаблонах приложения и может сообщать о найденных ошибках типов. В настоящее время Angular имеет три режима такой проверки, в зависимости от значения флагов fullTemplateTypeCheck
и strictTemplates
в конфигурационном файле TypeScript.
Базовый режим¶
В самом базовом режиме проверки типов, когда флаг fullTemplateTypeCheck
установлен в значение false
, Angular проверяет только выражения верхнего уровня в шаблоне.
Если написать <map [city]="user.address.city">
, то компилятор проверит следующее:
user
является свойством класса компонентаuser
является объектом со свойством addressuser.address
является объектом со свойством city
Компилятор не проверяет, что значение user.address.city
может быть присвоено входу city компонента <map>
.
В этом режиме компилятор также имеет ряд серьезных ограничений:
- Важно отметить, что он не проверяет встроенные представления, такие как
*ngIf
,*ngFor
, другие<ng-template>
встроенные представления. - Не проверяются типы
#refs
, результаты работы пайпов, тип$event
в привязках событий.
Во многих случаях эти вещи имеют тип any
, что может привести к тому, что последующие части выражения останутся без проверки.
Полный режим¶
Если флаг fullTemplateTypeCheck
установлен в true
, Angular более агрессивно проверяет типы в шаблонах. В частности:
- Проверяются встроенные представления (например, внутри
*ngIf
или*ngFor
). - Пайпы имеют правильный тип возврата
- Локальные ссылки на директивы и пайпы имеют правильный тип (за исключением любых общих параметров, которые будут иметь тип
any
)
Следующие параметры все еще имеют тип any
.
- Локальные ссылки на элементы DOM
- Объект
$event
- Выражения безопасной навигации
Флаг fullTemplateTypeCheck
был устаревшим в Angular 13. Вместо него следует использовать семейство опций компилятора strictTemplates
.
Строгий режим¶
Angular сохраняет поведение флага fullTemplateTypeCheck
и вводит третий "строгий режим". Строгий режим является супермножеством полного режима, и доступ к нему осуществляется путем установки флага strictTemplates
в true.
Этот флаг заменяет флаг fullTemplateTypeCheck
.
В строгом режиме Angular использует проверки, которые выходят за рамки проверки типов версии 8.
Строгий режим доступен только при использовании Ivy.
В дополнение к поведению в полном режиме, Angular делает следующее:
- Проверяет, что привязки компонентов/директив могут быть назначены на их
@Input()
. - Подчиняется флагу TypeScript
strictNullChecks
при проверке предшествующего режима - Определяет правильный тип компонентов/директив, включая дженерики
- Определяет типы контекста шаблона, если это настроено (например, позволяет правильно проверить тип
NgFor
) - Определяет правильный тип
$event
в привязках компонентов/директив, DOM и анимационных событий - Определяет правильный тип локальных ссылок на элементы DOM, основываясь на имени тега (например, тип, который
document.createElement
вернет для этого тега)
Проверка *ngFor
¶
Три режима проверки типов по-разному относятся к встроенным представлениям. Рассмотрим следующий пример.
1 2 3 4 5 6 7 |
|
1 2 3 4 |
|
Элементы <h2>
и <span>
находятся во встроенном представлении *ngFor
. В базовом режиме Angular не проверяет ни один из них. Однако в полном режиме Angular проверяет существование config
и user
и предполагает тип any
.
В строгом режиме Angular знает, что user
в <span>
имеет тип User
, и что address
— это объект со свойством city
типа string
.
Устранение ошибок шаблонов¶
В строгом режиме вы можете столкнуться с ошибками шаблонов, которые не возникали ни в одном из предыдущих режимов. Эти ошибки часто представляют собой настоящие несоответствия типов в шаблонах, которые не были пойманы предыдущими инструментами.
В этом случае в сообщении об ошибке должно быть ясно, в каком месте шаблона возникла проблема.
Также возможны ложные срабатывания, когда типизация библиотеки Angular неполная или неправильная, или когда типизация не совсем соответствует ожиданиям, как в следующих случаях.
-
Когда типизация библиотеки неверна или неполна (например, отсутствует
null | undefined
, если библиотека не была написана сstrictNullChecks
в уме). -
Когда типы ввода библиотеки слишком узкие, и библиотека не добавила соответствующие метаданные, чтобы Angular мог это понять.
Обычно это происходит при использовании атрибутов disabled или других распространенных булевых входов, например,
<input disabled>
. -
При использовании
$event.target
для событий DOM (из-за возможности пузырения событий,$event.target
в типизации DOM не имеет того типа, который вы могли бы ожидать).
В случае ложных срабатываний, подобных этим, есть несколько вариантов:
-
Использовать функцию
$any()
type-cast function в определенных контекстах, чтобы отказаться от проверки типов для части выражения. -
Отключить строгую проверку полностью, установив
strictTemplates: false
в конфигурационном файле TypeScript приложения,tsconfig.json
. -
Отключить определенные операции проверки типов по отдельности, сохраняя строгость в других аспектах, установив флаг строгости в
false
. -
Если вы хотите использовать
strictTemplates
иstrictNullChecks
вместе, откажитесь от строгой проверки нулевого типа специально для входных привязок, используяstrictNullInputTypes
.
Если не указано иное, каждый следующий параметр устанавливается в значение для strictTemplates
(true
, когда strictTemplates
— true
и наоборот).
strictInputTypes
- Проверяется ли присваиваемость выражения привязки полю
@Input()
. Также влияет на вывод директивных общих типов. strictInputAccessModifiers
- Учитываются ли модификаторы доступа, такие как
private
/protected
/readonly
, при присвоении выражения привязки к@Input()
. Если опция отключена, то модификаторы доступа для@Input
игнорируются; проверяется только тип. По умолчанию эта опция являетсяfalse
, даже если дляstrictTemplates
установлено значениеtrue
. strictNullInputTypes
- Соблюдается ли
strictNullChecks
при проверке привязок@Input()
(согласноstrictInputTypes
). Отключение этого параметра может быть полезно при использовании библиотеки, созданной без учетаstrictNullChecks
. strictAttributeTypes
- Проверять ли привязки
@Input()
, выполненные с использованием текстовых атрибутов. Например,<input matInput disabled="true">
(установка свойстваdisabled
в строку'true'
) против<input matInput [disabled]="true">
(установка свойстваdisabled
в булевоtrue
). strictSafeNavigationTypes
- Указывается ли возвращаемый тип операций безопасной навигации (например,
user?.name
будет корректно выводиться на основе типаuser
). Если отключено, тоuser?.name
будет иметь типany
. strictDomLocalRefTypes
- Будут ли локальные ссылки на элементы DOM иметь правильный тип. Если отключено, то
ref
будет иметь типany
для<input #ref>
. strictOutputEventTypes
- Будет ли
$event
иметь правильный тип для привязки событий к компоненту/директиве an@Output()
, или к событиям анимации. Если отключено, то будетлюбой
. strictDomEventTypes
- Будет ли
$event
иметь правильный тип для привязки к событиям DOM. Если отключено, то это будетany
. strictContextGenerics
- Будут ли корректно инфецироваться параметры типа общих компонентов (включая любые общие границы). Если отключено, то любые параметры типа будут иметь значение
any
. strictLiteralTypes
- Определять ли тип литералов объектов и массивов, объявленных в шаблоне. Если флаг отключен, то тип таких литералов будет
any
. Этот флаг имеет значениеtrue
, если для eitherfullTemplateTypeCheck
илиstrictTemplates
установлено значениеtrue
.
Если после устранения неполадок с помощью этих флагов у вас все еще возникают проблемы, вернитесь к полному режиму, отключив strictTemplates
.
Если это не сработает, то в крайнем случае можно полностью отключить полный режим с помощью fullTemplateTypeCheck: false
.
Ошибка проверки типов, которую вы не можете устранить ни одним из рекомендуемых методов, может быть результатом ошибки в самой системе проверки типов шаблонов. Если вы получаете ошибки, требующие возврата в базовый режим, то, скорее всего, это именно такая ошибка.
Если это произошло, file an issue, чтобы команда могла решить эту проблему.
Вводы и проверка типов¶
Средство проверки типов шаблонов проверяет, совместим ли тип выражения привязки с типом соответствующего входа директивы. В качестве примера рассмотрим следующий компонент:
1 2 3 4 5 6 7 8 9 10 11 |
|
Шаблон AppComponent
использует этот компонент следующим образом:
1 2 3 4 5 6 7 8 |
|
Здесь, во время проверки типа шаблона для AppComponent
, привязка [user]="selectedUser"
соответствует входу UserDetailComponent.user
. Поэтому Angular присваивает свойство selectedUser
свойству UserDetailComponent.user
, что привело бы к ошибке, если бы их типы были несовместимы.
TypeScript проверяет присвоение в соответствии со своей системой типов, подчиняясь таким флагам, как strictNullChecks
, поскольку они настроены в приложении.
Избегайте ошибок типа во время выполнения, предоставляя более конкретные требования к типу в шаблоне для проверки типа шаблона. Сделайте требования к входным типам для ваших собственных директив как можно более конкретными, предоставив функции защиты шаблона в определении директивы.
Смотрите Улучшение проверки типов шаблонов для пользовательских директив в этом руководстве.
Строгие проверки нуля¶
Когда вы включаете strictTemplates
и флаг TypeScript strictNullChecks
, в некоторых ситуациях могут возникать ошибки проверки типов, которых нелегко избежать. Например:
-
Нулевое значение, связанное с директивой из библиотеки, в которой не включена
strictNullChecks
.Для библиотеки, скомпилированной без
strictNullChecks
, ее файлы объявлений не будут указывать, может ли поле бытьnull
или нет.Для ситуаций, когда библиотека корректно обрабатывает
null
, это проблематично, так как компилятор будет проверять значение с нулевым значением на соответствие файлам объявления, в которых типnull
опущен.Таким образом, компилятор выдает ошибку проверки типов, поскольку он придерживается
strictNullChecks
. -
Использование пайпа
async
с Observable, который, как вы знаете, будет испускаться синхронно.Пайп
async
в настоящее время предполагает, что Observable, на которую она подписывается, может быть асинхронной, что означает, что возможно, что значение еще не доступно.В этом случае она все равно должна возвращать что-то — это
null
.Другими словами, тип возврата пайпа
async
включаетnull
, что может привести к ошибкам в ситуациях, когда известно, что Observable синхронно выдает значение, не являющееся nullable.
Существует два возможных обходных пути решения вышеупомянутых проблем:
-
В шаблоне включите оператор утверждения
!
в конце выражения с нулевым значением, например1
<user-detail [user]="user!"></user-detail>
В этом примере компилятор игнорирует несовместимость типов в nullability, как и в коде TypeScript.
В случае с пайпом
async
обратите внимание, что выражение необходимо обернуть круглыми скобками, как в примере1
<user-detail [user]="(user$ | async)!"></user-detail>
-
Полностью отключить строгую проверку нуля в шаблонах Angular.
Когда включена опция
strictTemplates
, все еще можно отключить некоторые аспекты проверки типов.Установка опции
strictNullInputTypes
вfalse
отключает строгую проверку нулевых типов в шаблонах Angular.Этот флаг применяется для всех компонентов, которые являются частью приложения.
Советы для авторов библиотек¶
Как автор библиотеки, вы можете предпринять несколько мер, чтобы обеспечить оптимальный опыт для ваших пользователей. Во-первых, включение strictNullChecks
и включение null
в тип ввода, по мере необходимости, сообщает вашим потребителям, могут ли они предоставить значение с нулевым значением или нет.
Кроме того, можно предоставлять подсказки типов, специфичные для проверки типов шаблонов.
Смотрите Улучшение проверки типов шаблонов для пользовательских директив, и Коэрцитивность входных сеттеров.
Принуждение входного сеттера¶
Иногда желательно, чтобы @Input()
директивы или компонента изменял привязанное к нему значение, обычно используя пару getter/setter для ввода. В качестве примера рассмотрим компонент пользовательской кнопки:
Рассмотрим следующую директиву:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Здесь вход disabled
компонента передается на <button>
в шаблоне. Все это работает, как и ожидалось, пока к входу привязано значение boolean
. Но, предположим, потребитель использует этот вход в шаблоне в качестве атрибута:
1 |
|
Это имеет тот же эффект, что и связывание:
1 |
|
Во время выполнения входное значение будет установлено в пустую строку, которая не является значением типа boolean
. Библиотеки компонентов Angular, которые решают эту проблему, часто "принуждают" значение к нужному типу в сеттере:
1 2 3 |
|
Идеально было бы изменить здесь тип value
с boolean
на boolean|''
, чтобы соответствовать набору значений, которые фактически принимаются сеттером. TypeScript до версии 4.3 требует, чтобы и геттер, и сеттер имели одинаковый тип, поэтому если геттер должен вернуть boolean
, то сеттер будет иметь более узкий тип.
Если у потребителя включена самая строгая проверка типов шаблонов Angular, это создает проблему: пустая строка (''
) не может быть присвоена полю disabled
, что создает ошибку типа при использовании формы атрибута.
В качестве обходного пути решения этой проблемы Angular поддерживает проверку более широкого, более допустимого типа для @Input()
, чем объявлено для самого поля ввода. Включите эту возможность, добавив в класс компонента статическое свойство с префиксом ngAcceptInputType_
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Начиная с TypeScript 4.3, сеттер может быть объявлен как принимающий boolean|''
в качестве типа, что делает поле принуждения входного сеттера устаревшим. Таким образом, поля принуждения входных сеттеров были устаревшими.
Это поле не обязательно должно иметь значение. Его существование сообщает системе проверки типов Angular, что вход disabled
следует рассматривать как принимающий привязки, соответствующие типу boolean|''
.
Суффиксом должно быть имя поля @Input
.
Следует обратить внимание на то, что если для данного входа существует переопределение ngAcceptInputType_
, то сеттер должен быть способен обрабатывать любые значения переопределенного типа.
Отключение проверки типов с помощью $any()
¶
Отключите проверку выражения привязки, окружив выражение вызовом псевдофункции $any()
cast pseudo-function. Компилятор рассматривает это как приведение к типу any
, как в TypeScript, когда используется приведение <any>
или as any
.
В следующем примере приведение person
к типу any
подавляет ошибку Property address does not exist
.
1 2 3 4 5 6 7 |
|