Предисловие
Написать заметку меня побудила статья Как устроена система типов typescript и собственный опыт.
Я обратил внимание, что не всегда понимал семантику «extends» в разных контекстах и влияние настроек языка.
В результате, то что меня смущало, оказалось рабочим поведением языка, но при этом непредсказуемым и небезопасным.
Введение
Для демонстрации я выбрал простое объединение string | number, которое буду помещать в различные контексты.
Пример 1:
type TA = number | string type TB = string type E1 = TA extends TB ? true : false // false type E2 = TB extends TA ? true : false // true export const example1 = () => { let a: TA = 100 let b: TB = 'value 100' b = a a = b return true }
Когда мы имеем дело с примитивными типами наше объединение будет выступать в определенном смысле супер типом для string или number.
И по описанному в статье, в примере 1 мы будем иметь некорректное нисходящее приведение b = a
и корректное восходящее приведение a = b
.
Пока все понятно, и предсказуемо.
Функции
Вариант 2:
type TC = (a: number | string) => void type TD = (a: string) => void type E3 = TC extends TD ? true : false type E4 = TD extends TC ? true : false let c: TC = a => undefined let d: TD = a => { if (typeof a !== 'string') { throw 'error a in d' } } const example2 = () => { c = d d = c return true }
Вроде по смыслу ничего не поменялось, но ts перестал работать как ожидаем. Здесь у нас работает явно противоречащие принципу небезопасное присваивание.
За такие финты ушами отвечает compilerOptions->strict
установленный в true
. А так же может быть использована отдельная опция strictFunctionTypes
.
Включение этих флажков решает проблему и мы получаем предупреждение о небезопасном присваивании.
Объекты
Адаптируем функцию выше как метод объекта.
Вариант 3:
interface IA { setValue(a: number | string): void } class CB implements IA { setValue(a: string) { if (typeof a !== 'string') { throw 'error a in CB' } } } const example3 = () => { const oIA: IA = new CB oIA.setValue(100) // Error: thrown: "error a in CB" return true }
Наш конфиг уже имеет флаг strict->true
. Но никаких ошибок вида ниже мы не получаем:
TS2322: Type TD is not assignable to type TC Types of parameters a and a are incompatible. Type string | number is not assignable to type string Type number is not assignable to type string
Это как раз тот самый момент, который и вызвал у меня вопросы.
Ответ нашелся в документации ts и звучит примерно так:
Во время разработки этой функции мы обнаружили большое количество изначально небезопасных иерархий классов, включая некоторые из них в DOM. В связи с этим настройка применяется только к функциям, написанным в синтаксисе функций, а не в синтаксисе методов
Обновлено:
Проблему можно решить, использовав стрелочную функцию:
interface IA { setValue: (a: number | string) => void }
Заключение
Выводов оказалось два.
-
Обязательно устанавливайте флажок
strict
. Он подключает дополнительные проверки типов. -
При реализации интерфейсов, и работе с объектами корректность соблюдения принципа подстановки Барбары Лисков ложится на плечи разработчиков.
-
использовать стрелочную форму записи метода
-
следить за импортированными типами
-
ссылка на оригинал статьи https://habr.com/ru/articles/847110/
Добавить комментарий