Здесь показаны различия между двумя версиями данной страницы.
| Предыдущая версия справа и слева Предыдущая версия | |||
|
typescript:types:type_assertion [2023/02/01 22:33] werwolf |
typescript:types:type_assertion [2023/02/01 22:35] (текущий) werwolf |
||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | ====== Приведение типов¶ ====== | + | ====== Приведение типов ====== |
| Получение значения, которое не соответствует ожидаемому типу, является обычным делом для типизированных языков. Понимание причин, лежащих в основе несоответствий, а также всевозможные способы их разрешений, являются целями данной главы. | Получение значения, которое не соответствует ожидаемому типу, является обычным делом для типизированных языков. Понимание причин, лежащих в основе несоответствий, а также всевозможные способы их разрешений, являются целями данной главы. | ||
| - | ===== Утверждение типов - общее¶ ===== | + | ===== Приведение типов - общее ===== |
| При разработке приложений на языках со статической типизацией время от времени может возникнуть нестыковка из-за несоответствия типов. Простыми словами, приходится работать с объектом, принадлежащим к известному типу, но ограниченному более специализированным (менее конкретным) интерфейсом. | При разработке приложений на языках со статической типизацией время от времени может возникнуть нестыковка из-за несоответствия типов. Простыми словами, приходится работать с объектом, принадлежащим к известному типу, но ограниченному более специализированным (менее конкретным) интерфейсом. | ||
| Строка 12: | Строка 12: | ||
| Возвращаясь к методу ''querySelector()'', стоит уточнить, что результатом его вызова может стать любой элемент, находящийся в dom-дереве. Если бы в качестве типа возвращаемого значения был указан тип ''HTMLElement'', то операция получения элемента ''<script>'' или ''<link>'' завершилась бы неудачей, так как они не принадлежат к этому типу. Именно поэтому методу ''querySelector()'' в качестве типа возвращаемого значения указан более базовый тип ''Element''. | Возвращаясь к методу ''querySelector()'', стоит уточнить, что результатом его вызова может стать любой элемент, находящийся в dom-дереве. Если бы в качестве типа возвращаемого значения был указан тип ''HTMLElement'', то операция получения элемента ''<script>'' или ''<link>'' завершилась бы неудачей, так как они не принадлежат к этому типу. Именно поэтому методу ''querySelector()'' в качестве типа возвращаемого значения указан более базовый тип ''Element''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| // <canvas id="stage" data-unactive="false"></canvas> | // <canvas id="stage" data-unactive="false"></canvas> | ||
| Строка 23: | Строка 24: | ||
| Попросить - дословно означает, что разработчик может лишь попросить вывод типов пересмотреть отношение к типу. Но решение разрешить операцию или нет все равно остается за последним. | Попросить - дословно означает, что разработчик может лишь попросить вывод типов пересмотреть отношение к типу. Но решение разрешить операцию или нет все равно остается за последним. | ||
| - | Выражаясь человеческим языком, в TypeScript процесс, вынуждающий вывод типов пересмотреть свое отношение к какому-либо типу называется утверждением типа (''Type Assertion''). | + | Выражаясь человеческим языком, в TypeScript процесс, вынуждающий вывод типов пересмотреть свое отношение к какому-либо типу называется Приведением типа (''Type Assertion''). |
| + | |||
| + | Формально Приведение типа похоже на преобразование (приведение) типов (type conversion, typecasting) но, поскольку в скомпилированном коде от типов не остается и следа, то по факту это совершенно другой механизм. Именно поэтому он и называется Приведение. Утверждая тип, разработчик говорит компилятору — “поверь мне, я знаю, что делаю” (Trust me, I know what I'm doing). | ||
| - | Формально утверждение типа похоже на преобразование (приведение) типов (type conversion, typecasting) но, поскольку в скомпилированном коде от типов не остается и следа, то по факту это совершенно другой механизм. Именно поэтому он и называется утверждение. Утверждая тип, разработчик говорит компилятору — “поверь мне, я знаю, что делаю” (Trust me, I know what I'm doing). | + | Нельзя не уточнить, что, хотя в TypeScript и существует термин Приведение типа, по ходу изложения в качестве синонимов будут употребляться слова преобразование, реже — приведение. А также, не будет лишним напомнить, что приведение — это процесс, в котором объект одного типа преобразуется в объект другого типа. |
| - | Нельзя не уточнить, что, хотя в TypeScript и существует термин утверждение типа, по ходу изложения в качестве синонимов будут употребляться слова преобразование, реже — приведение. А также, не будет лишним напомнить, что приведение — это процесс, в котором объект одного типа преобразуется в объект другого типа. | + | ===== Приведение типа синтаксис ===== |
| - | ===== Утверждение типа синтаксис¶ ===== | + | Одним из способов указать компилятору на принадлежность значения к заданному типу является механизм утверждения типа при помощи угловых скобок ''<ConcreteType>'', заключающих в себе конкретный тип, к которому и будет выполняться преобразование. Приведение типа располагается строго перед выражением, результатом выполнения которого будет преобразуемый тип. |
| - | Одним из способов указать компилятору на принадлежность значения к заданному типу является механизм утверждения типа при помощи угловых скобок ''<ConcreteType>'', заключающих в себе конкретный тип, к которому и будет выполняться преобразование. Утверждение типа располагается строго перед выражением, результатом выполнения которого будет преобразуемый тип. | + | <code javascript> |
| - | <code> | + | |
| <ToType>FromType; | <ToType>FromType; | ||
| </code> | </code> | ||
| Строка 38: | Строка 40: | ||
| Перепишем предыдущий код и исправим в нем ошибку, связанную с несоответствием типов. | Перепишем предыдущий код и исправим в нем ошибку, связанную с несоответствием типов. | ||
| - | <code> | + | <code javascript> |
| // <canvas id="stage" data-unactive="false"></canvas> | // <canvas id="stage" data-unactive="false"></canvas> | ||
| Строка 49: | Строка 51: | ||
| Если тип, к которому разработчик просит преобразовать компилятор, не совместим с преобразуемым типом, то в процессе утверждения возникнет ошибка. | Если тип, к которому разработчик просит преобразовать компилятор, не совместим с преобразуемым типом, то в процессе утверждения возникнет ошибка. | ||
| - | <code> | + | <code javascript> |
| class Bird { | class Bird { | ||
| public fly(): void {} | public fly(): void {} | ||
| Строка 64: | Строка 66: | ||
| Кроме того, существуют ситуации, в которых возникает необходимость множественного последовательного преобразования. Ярким примером являются значения, полученные от dom элементов, которые воспринимаются разработчиком как числовые или логические, но по факту принадлежат к строковому типу. | Кроме того, существуют ситуации, в которых возникает необходимость множественного последовательного преобразования. Ярким примером являются значения, полученные от dom элементов, которые воспринимаются разработчиком как числовые или логические, но по факту принадлежат к строковому типу. | ||
| - | <code> | + | <code javascript> |
| // <div id="#container"></div> | // <div id="#container"></div> | ||
| Строка 75: | Строка 77: | ||
| Дело в том, что в TypeScript невозможно привести тип ''string'' к типу ''number''. | Дело в том, что в TypeScript невозможно привести тип ''string'' к типу ''number''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| // <div id="#container"></div> | // <div id="#container"></div> | ||
| Строка 91: | Строка 94: | ||
| Но осуществить задуманное можно преобразовав тип ''string'' сначала в тип ''any'', а уже затем — в тип ''number''. | Но осуществить задуманное можно преобразовав тип ''string'' сначала в тип ''any'', а уже затем — в тип ''number''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| // <div id="#container"></div> | // <div id="#container"></div> | ||
| Строка 109: | Строка 113: | ||
| Стоит также заметить, что данный способ утверждения типа, кроме синтаксиса, больше ничем не отличается от указания с помощью оператора ''as''. | Стоит также заметить, что данный способ утверждения типа, кроме синтаксиса, больше ничем не отличается от указания с помощью оператора ''as''. | ||
| - | ===== Утверждение типа с помощью оператора as¶ ===== | + | |
| + | ===== Приведение типа с помощью оператора as ===== | ||
| В отличие от синтаксиса угловых скобок, которые указываются перед преобразуемым типом, оператор ''as'' указывается между преобразуемым и типом, к которому требуется преобразовать. | В отличие от синтаксиса угловых скобок, которые указываются перед преобразуемым типом, оператор ''as'' указывается между преобразуемым и типом, к которому требуется преобразовать. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| FromType as ToType; | FromType as ToType; | ||
| </code> | </code> | ||
| Строка 119: | Строка 125: | ||
| Обычное дело: при помощи метода ''querySelector()'' получить объект, принадлежащий к типу ''HTMLElement'', и подписать его на событие ''click''. Задача заключается в том, что при возникновении события нужно изменить значение поля ''dataset'', объявленного в типе ''HTMLElement''. Было бы нерационально снова получать ссылку на объект при помощи метода ''querySelector()'', ведь нужный объект хранится в свойстве объекта события ''target''. Но дело в том, что свойство ''target'' имеет тип ''EventTarget'', который не находится в иерархической зависимости с типом ''HTMLElement'', имеющим нужное свойство ''dataset''. | Обычное дело: при помощи метода ''querySelector()'' получить объект, принадлежащий к типу ''HTMLElement'', и подписать его на событие ''click''. Задача заключается в том, что при возникновении события нужно изменить значение поля ''dataset'', объявленного в типе ''HTMLElement''. Было бы нерационально снова получать ссылку на объект при помощи метода ''querySelector()'', ведь нужный объект хранится в свойстве объекта события ''target''. Но дело в том, что свойство ''target'' имеет тип ''EventTarget'', который не находится в иерархической зависимости с типом ''HTMLElement'', имеющим нужное свойство ''dataset''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| // <span id="counter"></span> | // <span id="counter"></span> | ||
| Строка 133: | Строка 140: | ||
| Но эту проблему легко решить с помощью оператора утверждения типа ''as''. Кроме того, с помощью этого же оператора можно привести тип ''string'', к которому принадлежат все свойства, находящиеся в ''dataset'', к типу ''any'', а уже затем к типу ''number''. | Но эту проблему легко решить с помощью оператора утверждения типа ''as''. Кроме того, с помощью этого же оператора можно привести тип ''string'', к которому принадлежат все свойства, находящиеся в ''dataset'', к типу ''any'', а уже затем к типу ''number''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| let element = document.querySelector( | let element = document.querySelector( | ||
| '#counter' | '#counter' | ||
| Строка 150: | Строка 158: | ||
| В случае несовместимости типов возникнет ошибка. | В случае несовместимости типов возникнет ошибка. | ||
| - | <code> | + | <code javascript> |
| class Bird { | class Bird { | ||
| public fly(): void {} | public fly(): void {} | ||
| Строка 166: | Строка 174: | ||
| Факт, что над значением, принадлежащим к типу ''any'', разрешено выполнение любых операций, означает, что компилятор их не проверяет. Другими словами, разработчик, указывая тип ''any'', усложняет процесс разработки, мешая компилятору проводить статический анализ кода, а также лишает себя помощи со стороны редактора кода. Когда разработчику известно, к какому типу принадлежит значение, можно попросить компилятор изменить мнение о принадлежности значения к его типу с помощью механизма утверждения типов. | Факт, что над значением, принадлежащим к типу ''any'', разрешено выполнение любых операций, означает, что компилятор их не проверяет. Другими словами, разработчик, указывая тип ''any'', усложняет процесс разработки, мешая компилятору проводить статический анализ кода, а также лишает себя помощи со стороны редактора кода. Когда разработчику известно, к какому типу принадлежит значение, можно попросить компилятор изменить мнение о принадлежности значения к его типу с помощью механизма утверждения типов. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| class DataProvider { | class DataProvider { | ||
| constructor(readonly data: any) {} | constructor(readonly data: any) {} | ||
| Строка 181: | Строка 190: | ||
| </code> | </code> | ||
| - | Напоследок, стоит сказать, что выражения, требующие утверждения типа при работе с dom api — это неизбежность. Кроме того, для работы с методом ''document.querySelector()'', который был использован в примерах к этой главе, вместо приведения типов с помощью операторов ''<Type>'' или ''as'' предпочтительней конкретизировать тип с помощью обобщения, которое рассматривается в главе [[:typescript:types|Обобщения (Generics)]]. Но в случае, если утверждение требуется для кода, написанного самим разработчиком, то скорее всего, это следствие плохо продуманной архитектуры. | + | Напоследок, стоит сказать, что выражения, требующие утверждения типа при работе с dom api — это неизбежность. Кроме того, для работы с методом ''document.querySelector()'', который был использован в примерах к этой главе, вместо приведения типов с помощью операторов ''<Type>'' или ''as'' предпочтительней конкретизировать тип с помощью обобщения, которое рассматривается в главе [[.|Обобщения (Generics)]]. Но в случае, если Приведение требуется для кода, написанного самим разработчиком, то скорее всего, это следствие плохо продуманной архитектуры. |
| - | ===== Приведение (утверждение) к константе (const assertion)¶ ===== | + | ===== Приведение (Приведение) к константе (const assertion) ===== |
| Ни для кого не секрет, что с точки зрения JavaScript, а, следовательно, и TypeScript, все примитивные литеральные значения являются константными значениями. С точки зрения среды исполнения два эквивалентных литерала любого литерального типа являются единым значением. То есть, среда исполнения расценивает два строковых литерала '''text''' и '''text''' как один литерал. То же справедливо и для остальных литералов, к которым помимо типа ''string'' также относятся типы ''number'', ''boolean'' и ''symbol''. | Ни для кого не секрет, что с точки зрения JavaScript, а, следовательно, и TypeScript, все примитивные литеральные значения являются константными значениями. С точки зрения среды исполнения два эквивалентных литерала любого литерального типа являются единым значением. То есть, среда исполнения расценивает два строковых литерала '''text''' и '''text''' как один литерал. То же справедливо и для остальных литералов, к которым помимо типа ''string'' также относятся типы ''number'', ''boolean'' и ''symbol''. | ||
| - | Тем не менее сложно найти разработчика TypeScript, не испытавшего трудностей, создаваемых выводом типов при определении конструкций, которым предстоит проверка на принадлежность к литеральному типу..<code> | + | Тем не менее сложно найти разработчика TypeScript, не испытавшего трудностей, создаваемых выводом типов при определении конструкций, которым предстоит проверка на принадлежность к литеральному типу.. |
| + | |||
| + | <code javascript> | ||
| type Status = 200 | 404; | type Status = 200 | 404; | ||
| type Request = { status: Status }; | type Request = { status: Status }; | ||
| Строка 196: | Строка 207: | ||
| </code> | </code> | ||
| - | В коде выше ошибка возникает по причине того, что вывод типов определяет принадлежность значения переменной ''status'' к типу ''number'', а не литеральному числовому типу ''200''.<code> | + | В коде выше ошибка возникает по причине того, что вывод типов определяет принадлежность значения переменной ''status'' к типу ''number'', а не литеральному числовому типу ''200''. |
| + | |||
| + | <code javascript> | ||
| // вывод типов видит как | // вывод типов видит как | ||
| let status: number = 200; | let status: number = 200; | ||
| Строка 205: | Строка 218: | ||
| Прежде всего не будет лишним упомянуть, что данную проблему можно решить с помощью механизма утверждения при помощи таких операторов как ''as'' и угловых скобок ''<>''. | Прежде всего не будет лишним упомянуть, что данную проблему можно решить с помощью механизма утверждения при помощи таких операторов как ''as'' и угловых скобок ''<>''. | ||
| - | <code> | + | |
| + | <code javascript> | ||
| type Status = 200 | 404; | type Status = 200 | 404; | ||
| type Request = { status: Status }; | type Request = { status: Status }; | ||
| Строка 218: | Строка 232: | ||
| </code> | </code> | ||
| - | Но лучшим решением будет специально созданный для подобных случаев механизм, позволяющий производить утверждение к константе. | + | Но лучшим решением будет специально созданный для подобных случаев механизм, позволяющий производить Приведение к константе. |
| - | Константное утверждение производится с помощью оператора ''as'' или угловых скобок ''<>'' и говорит компилятору, что значение является константным.<code> | + | Константное Приведение производится с помощью оператора ''as'' или угловых скобок ''<>'' и говорит компилятору, что значение является константным. |
| + | |||
| + | <code javascript> | ||
| type Status = 200 | 404; | type Status = 200 | 404; | ||
| type Request = { status: Status }; | type Request = { status: Status }; | ||
| Строка 230: | Строка 246: | ||
| </code> | </code> | ||
| - | Утверждение, что значение является константным, заставляет вывод типов расценивать его как принадлежащее к литеральному типу. Утверждение к константе массива заставляет вывод типов определять его принадлежность к типу ''readonly tuple''.<code> | + | Приведение, что значение является константным, заставляет вывод типов расценивать его как принадлежащее к литеральному типу. Приведение к константе массива заставляет вывод типов определять его принадлежность к типу ''readonly tuple''. |
| + | |||
| + | <code javascript> | ||
| let a = [200, 404]; // let a: number[] | let a = [200, 404]; // let a: number[] | ||
| Строка 237: | Строка 255: | ||
| </code> | </code> | ||
| - | В случае с объектным типом, утверждение к константе рекурсивно помечает все его поля как ''readonly''. Кроме того, все его поля, принадлежащие к примитивным типам, расцениваются как литеральные типы. | + | В случае с объектным типом, Приведение к константе рекурсивно помечает все его поля как ''readonly''. Кроме того, все его поля, принадлежащие к примитивным типам, расцениваются как литеральные типы. |
| - | <code> | + | |
| + | <code javascript> | ||
| type NotConstResponseType = { | type NotConstResponseType = { | ||
| status: number; | status: number; | ||
| Строка 259: | Строка 278: | ||
| </code> | </code> | ||
| - | Но стоит помнить, что утверждение к константе применимо исключительно к литералам таких типов, как ''number'', ''string'', ''boolean'', ''array'' и ''object''.<code> | + | Но стоит помнить, что Приведение к константе применимо исключительно к литералам таких типов, как ''number'', ''string'', ''boolean'', ''array'' и ''object''. |
| + | |||
| + | <code javascript> | ||
| let a = 'value' as const; // Ok - 'value' является литералом, let a: "value" | let a = 'value' as const; // Ok - 'value' является литералом, let a: "value" | ||
| let b = 100 as const; // Ok - 100 является литералом, let b: 100 | let b = 100 as const; // Ok - 100 является литералом, let b: 100 | ||
| Строка 280: | Строка 301: | ||
| В случае, когда литералы ссылочных типов (массивы и объекты) ассоциированы со значением, также принадлежащим к ссылочному типу, то они представляются такими, какими были на момент ассоциации. Кроме того, поведение механизма приведения к константе зависит от другого механизма — деструктуризации. | В случае, когда литералы ссылочных типов (массивы и объекты) ассоциированы со значением, также принадлежащим к ссылочному типу, то они представляются такими, какими были на момент ассоциации. Кроме того, поведение механизма приведения к константе зависит от другого механизма — деструктуризации. | ||
| - | <code> | + | <code javascript> |
| let defaultObject = { f: 100 }; // let defaultObject: {f: number;} | let defaultObject = { f: 100 }; // let defaultObject: {f: number;} | ||
| let constObject = { f: 100 } as const; // let constObject: {readonly f: 100;} | let constObject = { f: 100 } as const; // let constObject: {readonly f: 100;} | ||
| Строка 306: | Строка 327: | ||
| По причине, что объектные типы данных, хранящиеся в массиве, подчиняются описанным выше правилам, подробное рассмотрение процесса утверждения массива к константе будет опущено. | По причине, что объектные типы данных, хранящиеся в массиве, подчиняются описанным выше правилам, подробное рассмотрение процесса утверждения массива к константе будет опущено. | ||
| - | И последнее, о чем стоит упомянуть — утверждение к константе применимо только к простым выражениям. | + | И последнее, о чем стоит упомянуть — Приведение к константе применимо только к простым выражениям. |
| - | <code> | + | <code javascript> |
| let a = (Math.round(Math.random() * 1) | let a = (Math.round(Math.random() * 1) | ||
| ? 'yes' | ? 'yes' | ||
| Строка 317: | Строка 338: | ||
| </code> | </code> | ||
| - | ===== Утверждение в сигнатуре (Signature Assertion)¶ ===== | + | ===== Приведение в сигнатуре (Signature Assertion) ===== |
| - | Помимо функций, реализующих механизм утверждения типа, в TypeScript существует механизм утверждения в сигнатуре, позволяющий определять утверждающие функции, вызов которых, в случае невыполнения условия, приводит к выбрасыванию исключения. Для того чтобы объявить утверждающую функцию, в её сигнатуре (там, где располагается возвращаемое значение) следует указать ключевое слово ''asserts'', а затем параметр принимаемого на вход условия.<code> | + | Помимо функций, реализующих механизм утверждения типа, в TypeScript существует механизм утверждения в сигнатуре, позволяющий определять утверждающие функции, вызов которых, в случае невыполнения условия, приводит к выбрасыванию исключения. Для того чтобы объявить утверждающую функцию, в её сигнатуре (там, где располагается возвращаемое значение) следует указать ключевое слово ''asserts'', а затем параметр принимаемого на вход условия. |
| + | |||
| + | <code javascript> | ||
| function identifier(condition: any): asserts condition { | function identifier(condition: any): asserts condition { | ||
| if (!condition) { | if (!condition) { | ||
| Строка 331: | Строка 354: | ||
| Если принадлежность значения к указанному типу подтверждается, то далее по коду компилятор будет рассматривать его в роли этого типа. Иначе выбрасывается исключение. | Если принадлежность значения к указанному типу подтверждается, то далее по коду компилятор будет рассматривать его в роли этого типа. Иначе выбрасывается исключение. | ||
| - | <code> | + | <code javascript> |
| - | // утверждение в сигнатуре | + | // Приведение в сигнатуре |
| function isStringAssert(condition: any): asserts condition { | function isStringAssert(condition: any): asserts condition { | ||
| if (!condition) { | if (!condition) { | ||
| Строка 339: | Строка 362: | ||
| } | } | ||
| - | // утверждение типа | + | // Приведение типа |
| function isString(value: any): value is string { | function isString(value: any): value is string { | ||
| return typeof value === 'string'; | return typeof value === 'string'; | ||
| Строка 351: | Строка 374: | ||
| isStringAssert(isString(text)); // механизм "утверждения типа" | isStringAssert(isString(text)); // механизм "утверждения типа" | ||
| - | text.touppercase(); // ..после утверждениея как тип string | + | text.touppercase(); // ..после Приведениея как тип string |
| }; | }; | ||
| </code> | </code> | ||
| Строка 357: | Строка 380: | ||
| При использовании механизма утверждения в сигнатуре с механизмом утверждения типа, условие из вызова утверждающей функции можно перенести в её тело. | При использовании механизма утверждения в сигнатуре с механизмом утверждения типа, условие из вызова утверждающей функции можно перенести в её тело. | ||
| - | <code> | + | <code javascript> |
| function isStringAsserts( | function isStringAsserts( | ||
| value: any | value: any | ||
| Строка 377: | Строка 400: | ||
| Стоит обратить внимание на то, что механизм утверждения типа не будет работать в случае переноса условного выражения в тело утверждающей функции, сигнатура которой лишена утверждения типов и содержит исключительно утверждения в сигнатуре. | Стоит обратить внимание на то, что механизм утверждения типа не будет работать в случае переноса условного выражения в тело утверждающей функции, сигнатура которой лишена утверждения типов и содержит исключительно утверждения в сигнатуре. | ||
| - | <code> | + | <code javascript> |
| function isStringAsserts( | function isStringAsserts( | ||
| value: any | value: any | ||
| Строка 391: | Строка 414: | ||
| isStringAsserts(text); // условие определено в утверждающей функции | isStringAsserts(text); // условие определено в утверждающей функции | ||
| - | text.touppercase(); // нет ошибки, потому что утверждение типов не работает | + | text.touppercase(); // нет ошибки, потому что Приведение типов не работает |
| }; | }; | ||
| </code> | </code> | ||
| - | |||
| - | |||