Оператор keyof, Lookup Types, Mapped Types, Mapped Types - префиксы + и -¶
Для того, чтобы повысить уровень выявления ошибок и при этом сократить время разработки программы, создатели TypeScript не прекращают радовать разработчиков добавлением новых возможностей для взаимодействия с типами данных. Благодаря усилиям разработчиков со всего земного шара, стало осуществимо получать тип объединение, полученный на основе как ключей конкретного типа, так и ассоциированных с ними типами. Кроме этого, возможность определять новый тип в процессе итерации другого типа, используя при этом различные выражения, превращает типизированный мир в фантастическую страну. Единственное, что требуется от разработчика, это понимание этих процессов, которым и будет посвящена текущая глава.
Запрос ключей keyof¶
В TypeScript существует возможность выводить все публичные, нестатические, принадлежащие типу ключи и на их основе создавать литеральный объединенный тип (Union). Для получения ключей нужно указать оператор keyof, после которого указывается тип, чьи ключи будут объединены в тип объединение keyof Type.
Оператор keyof может применяться к любому типу данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Как уже было замечено, оператор keyof выводит только публичные нестатические ключи типа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
В случае, если тип данных не содержит публичных ключей, оператор keyof выведет тип never.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Оператор keyof также может использоваться в объявлении обобщенного типа данных. Точнее, с помощью оператора keyof можно получить тип, а затем расширить его параметром типа. Важно понимать, что в качестве значения по умолчанию может выступать только тип, совместимый с объединенным типом, полученным на основе ключей.
1 | |
Напоследок стоит упомянуть об одном неочевидном моменте: оператор keyof можно совмещать с оператором typeof (Type Queries).
1 2 3 4 5 6 7 8 9 | |
Поиск типов (Lookup Types)¶
Если оператор keyof выбирает все доступные ключи, то с помощью поиска типов можно получить заданные типы по известным ключам. Получить связанный с ключом тип можно с помощью скобочной нотации, в которой через оператор вертикальная черта | будут перечислены от одного и более ключа, существующего в типе. В качестве типа данных могут выступать только интерфейсы, классы и в ограниченных случаях операторы типа.
В случаях, когда в качестве типа данных выступает интерфейс, то получить можно все типы без исключения. При попытке получить тип несуществующего ключа возникнет ошибка.
1 2 3 4 5 6 7 8 9 | |
Если в качестве типа выступает класс, то получить типы можно только у членов его экземпляра. При попытке получить тип несуществующего члена возникнет ошибка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Нельзя переоценить вклад возможностей поиска типов, которые пришлись на динамическую часть типизированного мира TypeScript. Благодаря поиску типов в паре с оператором keyof появилась возможность, позволяющая выводу типов устанавливать связь между динамическими ключами и их типами. Это в свою очередь позволяет производить дополнительные проверки, которые повышают типобезопасность кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Сопоставление типов (Mapped Types)¶
Сопоставленные типы — это типы данных, которые при помощи механизма итерации модифицируют лежащие в основе конкретные типы данных.
В TypeScript существует возможность определения типа, источником ключей которого выступает множество, определяемое литеральными строковыми типами. Подобные типы обозначаются как сопоставленные типы (Mapped Types) и определяются исключительно на основе псевдонимов типов (Type Alias), объявление которых осуществляется при помощи ключевого слова type. Тело сопоставимого типа, заключенное в фигурные скобки {}, включает в себя одно единственное выражение, состоящее из двух частей, разделенных двоеточием.
1 2 3 | |
В левой части выражения располагается обрамленное в квадратные скобки [] выражение, предназначенное для работы с множеством, а в правой части определяется произвольный тип данных.
1 2 3 | |
Выражение, описывающее итерацию элементов, представляющих ключи, также состоит из двух частей, разделяемых оператором in ([ЛевыйОперанд in ПравыйОперанд]). В качестве левого операнда указывается произвольный идентификатор, который в процессе итерации последовательно будет ассоциирован со значениями множества, указанного в правой части ([ПроизвольныйИдентификатор in Множество]).
1 2 3 | |
Как уже было сказано, в роли идентификатора может выступать любой идентификатор.
1 2 3 4 5 6 7 8 9 | |
Множество может быть определено как единственным литеральным строковым типом (ElementLiteralStringType), так и его множеством, составляющим тип объединение (Union Type) (FirstElementLiteralStringType | SecondElementLeteralStringType).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Результатом определения сопоставленного типа является новый объектный тип, состоящий из ключей, ассоциированных с произвольным типом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
От статического указания итерируемого типа мало пользы, поэтому Mapped Types лучше всего раскрывают свой потенциал при совместной работе с известными к этому моменту запросом ключей (keyof) и поиском типов (Lookup Types,) оперирующих параметрами типа (Generics).
1 2 3 4 5 6 7 8 9 | |
В первом случае в выражении [P in keyof T]: T[P]; первым действием выполняется вычисление оператора keyof над параметром типа T. В его результате ключи произвольного типа преобразуются во множество, то есть в тип Union, элементы которого принадлежат к литеральному строковому типу данных. Простыми словами, операция keyof T заменяется на только что полученный тип Union [P in Union]: T[P];, над которым на следующем действии выполняется итерация.
Во втором случае MappedType<T, U extends keyof T> оператор keyof также преобразует параметр типа T в тип Union, который затем расширяет параметр типа U, тем самым получая все его признаки, необходимые для итерации в выражении [K in U].
С полученным в итерации [K in U] ключом K ассоциируется тип, ассоциированный с ним в исходном типе и полученный с помощью механизма поиска типов T[K].
Совокупность описанных механизмов позволяет определять не только новый тип, но и создавать модифицирующие типы, которые будут способны добавлять модификаторы, как например readonly или ?:.
1 2 3 4 5 6 7 8 9 10 | |
Как уже было замечено, в правой части выражения можно указать любой тип данных, в том числе и объединение, включающеее тип, полученный при помощи механизма поиска типов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Сопоставленные типы не могут содержать более одной итерации в типе, а также не могут содержать объявление других членов.
1 2 3 4 5 | |
К тому же в TypeScript существует несколько готовых типов, таких как Readonly<T>, Partial<T>, Record<K, T> и Pick<T, K> (глава “Readonly, Partial, Required, Pick, Record).
Кроме того, сопоставленные типы вместе с шаблонными литеральными строковыми типами способны переопределить исходные ключи при помощи ключевого слова as, указываемого после строкового перечисления.
1 2 3 | |
Таким образом, совмещая данный механизм с шаблонными литеральными строковыми типами, можно добиться переопределения исходных ключей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Префиксы + и - в сопоставленных типах¶
Сопоставленные типы позволяют добавлять модификаторы, но не позволяют их удалять, что в свою очередь имеет большое значение в случае с гомоморфными типами, которые по умолчанию сохраняют модификаторы своего базового типа (гомоморфные типы будут рассмотрены в главе Readonly, Partial, Required, Pick, Record).
Для разрешения данной проблемы к модификаторам в типах сопоставления были добавлены префиксы + и -, с помощью которых реализуется поведение модификатора — добавить (+) или удалить (-).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |