TypeScript — это язык программирования для разработки современных веб-приложений, расширяющий возможности уже традиционного JavaScript. В своей статье редакция highload.tech разобралась, что такое TypeScript, какими он обладает преимуществами и особенностями.
Содержание:
1. Что такое TypeScript?
2. Отличия TypeScript от JavaScript
3. Установка TypeScript
4. Как работает TypeScript?
5. Особенности TypeScript
6. Пишем приложение на TypeScript
7. Преимущества и недостатки TypeScript
Итоги
TypeScript — это язык со статической типизацией на основе JavaScript. Он создан в 2012 году в компании Microsoft. Разработкой TypeScript занимался Андрес Гейлсберг (тот самый, который был ведущим архитектором C# и Delphi, а также создал Turbo Pascal).
Для переменных и других структур данных можно объявить определенный тип, например, строковый или булевский, и TypeScript будет проверять, допустимые ли значения им присваиваются. Это невозможно сделать в JavaScript, в котором типизация нестрогая.
В TypeScript можно явно указать тип переменной. Когда тип переменной определен, ей невозможно присвоить значение, которое относится к другому типу.
В TypeScript реализованы дополнительные возможности ООП, такие как интерфейсы, поля объекта и модификаторы доступа.
Интерфейс определяется с помощью ключевого слова interface
.
// Определить интерфейс домашнего животного interface IPet { name: string, species: string, breed: string, speak: () => string } // Создать пса var dog:IPet = { name: "Ritchie Blaсkdog", species: "dog", breed: "hellhound", speak: ():string => {return "Woof! Woof-woof-woof!"} }
Поля и модификаторы доступа:
class User { // Поля будут приватными private name: string; private password: string; constructor(name: string, password: string){ this.name = name; this.password = password; } // Доступ для чтения открыт public get Name(): string{ return this.name; } public get Password(): string{ return this.password; } }
JavaScript создан для написания небольших сценариев веб-страниц. Чем крупнее приложение, тем более громоздким становится код на JS.
Поэтому был создан TypeScript, который упрощает разработку широкомасштабных приложений.
Из-за нестрогой типизации в JavaScript многие ошибки видны только во время выполнения. Это неудобно и, в конце концов, неприятно. Приходится тратить много времени на поиск таких ошибок.
TypeScript устраняет этот недостаток: написанный на нем сценарий не сможет случайно вызвать переменную как функцию или запросить значение поля, если оно не определено в объекте. Вы увидите превентивное сообщение об ошибке уже во время набора кода.
В TypeScript реализована поддержка интерфейсов и модификаторов доступа, обобщений, модулей, пространств имен и окружений (ambients
). В JavaScript этого нет.
Код JavaScript — интерпретируемый язык. Он воспринимается браузерами напрямую. TypeScript компилируется в JavaScript.
Осваивать язык программирования лучше всего на практике. Для тестирования небольших фрагментов кода существует TypeScript Playground. Здесь вы можете набирать код, просматривать ошибки, журнал, код JavaScript, в который скомпилирован ваш код на TypeScript, настраивать среду по своему усмотрению, экспортировать код и скопировать ссылку, чтобы поделиться им.
Бонус — код копируется с форматированием.
Для разработки проектов вам понадобится установить компилятор TypeScript. Его можно установить с помощью менеджера зависимостей для Node.js: npm, yarn или pnpm.
Предположим, что Node.js и менеджер зависимостей npm установлены. Создадим папку проекта и в командной строке запустим такую команду:
npm install typescript --save-dev
С помощью этой команды установки создается локальная копия TypeScript в папке проекта. Для рабочих проектов рекомендуется использовать именно такую установку.
Можно установить TypeScript и глобально:
npm install -g typescript
В этом случае команда tsc
будет доступна в любом расположении. Глобальную установку можно использовать для одноразовых испытаний.
Теперь проверим установку. В этой же папке создадим файл dog.ts
(файлы TypeScript имеют расширение ts
).
Добавим в него код из примера про пса, который приведен выше, и сохраним файл. В командной строке введем:
npx tsc dog.ts
После выполнения команды получим файл dog.js
с таким кодом:
// Создать пса var dog = { name: "Ritchie Blaсkdog", species: "dog", breed: "hellhound", speak: function () { return "Woof! Woof-woof-woof!"; } };
С помощью Deno можно выполнять файлы TypeScript прямо в консоли. Добавим в файл dog.ts
такую строку:
console.log(`${dog.name} says ${dog.speak()}`)
Запустим команду:
deno run dog.ts
И получим результат:
В TypeScript можно объявить переменную четырьмя способами: с указанием типа и значения, с указанием только типа, с указанием только значения — и без типа, и без значения.
// Объявить тип и значение в одном операторе. var amount:number = 10000.00; /* Объявить тип, не присваивая значения. По умолчанию переменной будет присвоено значение undefined. */var amount:number; /* Объявить значение, не задавая тип. Переменная получит тип присвоенного значения. */var amount = 10000.00; /* Не указывать ни типа, ни значения. В этом случае переменная получит тип any и значение undefined. */var amount;
После того как объявлен тип переменной, ей невозможно присвоить значение другого типа.
Но если, все-таки, необходимо изменить тип переменной, то можно воспользоваться утверждением типа (type assertion
). Для этого нужно заключить требуемый тип в символы < >
и поместить перед переменной или выражением.
var x = '1' var y:number = <number> <any> x console.log(typeof(y))
Приведенный выше код выведет в консоль строку «string
».
Рассмотрим типы данных, определенные в TypeScript.
Тип any
— это надтип для всех типов в TypeScript, динамический тип. Его использование подразумевает отказ от проверки типа переменной.
В приведенной ниже таблице перечислены встроенные типы TypeScript и даны их описания.
Ключевое слово | Описание |
number | 64-разрядные числовые значения с плавающей точкой. Используются для представления как целых, так и дробных чисел. |
string | Последовательность символов Unicode . |
boolean | Логическое значение: true или false |
void | Тип возврата для функций, которые не возвращают значения |
null | Явное указание отсутствия значения объекта. |
undefined | Значение, присваиваемое всем переменным до их инициализации |
В приведенной ниже таблице перечислены пользовательские типы TypeScript и даны их описания.
Тип | Описание |
массив (array)
| Эти типы позволяют хранить множеству несколько значений в заданной последовательности. Массив состоит из элементов одного типа, а кортеж может содержать значения нескольких разных типов. Для обращения к каждому элементу массива используется метод TypeScript forEach() . |
перечисление (enum) | Как и в C#, enum дает возможность присвоить удобочитаемые имена последовательности числовых значений. |
объединение (union) | Начиная с TypeScript 1.4 можно комбинировать типы, благодаря чему переменная может принимать значение, относящееся к одному из них. |
объект (object) | Представляет собой любое значение, не относящееся к примитивному типу. |
Типами в TypeScript могут быть не только строки вообще (string
) или числа вообще (number
). Конкретное число или конкретная строка тоже может быть типом. Например, типом может быть «8
», а может быть «красный
». Если переменная объявлена с литералом типа, то она не может принимать никакие другие значения.
Рассмотрим это на примере кода:
let red:"красный" = "красный"; red = "красный"; red = "зеленый";
При попытке его скомпилировать получим ошибку:
И для чего же нужна переменная с уникальным типом и единственным значением?
Сама по себе она не особо полезна. Дело в применении.
Например, в CSS есть свойства, которые принимают лишь несколько определенных значений. Свойство alignment
может принимать значения left, right
или center
.
Если объединить литералы типов left, right
и center
(которые объявлены как строки) в union
, то получим двойную пользу:
Наглядно это будет выглядеть так:
function setText(msg: string, alignment: "left" | "right" | "center") { // ... } setText("Готово!", "left"); setText("Ошибка!", "bottom");
Компилятор не дает ошибиться:
Шаблоны — это надстройка над литералами типов. Благодаря объединениям они могут разворачиваться во множество строк без необходимости в циклах.
В шаблонах литеральных типов используется тот же синтаксис, что и в шаблонах литералов строк JavaScript, но с указанием типов, а не переменных.
Если же тип определен в виде объединения, то типу присваивается каждый возможный литерал из комбинаций членов объединения.
Если добавить еще один критерий, например, валюту, то будет создано множество из всех возможных комбинаций.
То есть, весь список значений для этого типа будет таким:
type cardsByCurrencies = "USD_prepaid_visa" | "USD_prepaid_mastercard" | "USD_credit_visa" | "USD_credit_mastercard" | "EUR_prepaid_visa" | "EUR_prepaid_mastercard" | "EUR_credit_visa" | "EUR_credit_mastercard"
Необходимо также заметить, что и в литералах типов, равно как и в шаблонах, может использоваться вывод типов. Подробнее см. в документации.
TypeScript — это объектно-ориентированный JavaScript. Он поддерживает классы, интерфейсы и т. п. TypeScript получил поддержку классов от ES6.
Для объявления класса используется ключевое слово class
.
class User { // Область видимости класса }
Приведенный выше код компилируется в такой код JavaScript.
var User = /** @class */ (function () { function User() { } return User; }());
Приведем пример объявления класса с полями, конструктором и функцией.
class Person { // Поле firstName: string; lastName: string; // Конструктор constructor(firstName:string, lastName: string) { this.firstName = firstName; this.lastName = lastName; } // Функция logFullName(): void { console.log(`${this.firstName} ${this.lastName}`); } }
На JavaScript этот код будет выглядеть так:
var Person = /** @class */ (function () { // Конструктор function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Функция Person.prototype.logFullName = function () { console.log("".concat(this.firstName, " ").concat(this.lastName)); }; return Person; }());
Экземпляр класса в TypeScript создается с помощью ключевого слова new
.
var p = new Person('Mickey', 'Mouse');
Для обращения к полям и функциям используется запись с точкой.
p.firstName; p.logFullName();
Сведем все воедино:
class Person { // Поле firstName: string; lastName: string; // Конструктор constructor(firstName:string, lastName: string) { this.firstName = firstName; this.lastName = lastName; } // Функция logFullName(): void { console.log(`${this.firstName} ${this.lastName}`); } } var p = new Person('Mickey', 'Mouse'); console.log(`Имя: ${p.firstName}`); console.log(`Фамилия: ${p.lastName}`); console.log('Полное имя:'); p.logFullName();
Код JavaScript:
var Person = /** @class */ (function () { // Конструктор function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Функция Person.prototype.logFullName = function () { console.log("".concat(this.firstName, " ").concat(this.lastName)); }; return Person; }()); var p = new Person('Mickey', 'Mouse'); console.log("\u0418\u043C\u044F: ".concat(p.firstName)); console.log("\u0424\u0430\u043C\u0438\u043B\u0438\u044F: ".concat(p.lastName)); console.log('Полное имя:'); p.logFullName();
Вывод:
Как видите, если вы знакомы с ООП и JavaScript, все довольно просто.
Пространства имен — это способ логической группировки взаимосвязанного кода. Это встроенная возможность TypeScript. В JavaScript пространства имен отсутствуют, поэтому может возникнуть конфликт имен, если в нескольких файлах встретятся переменные с одним и тем же именем.
Пример
Файл conflict.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Конфликт имен в JavaScript</title> <script src="scr1.js"></script> <script src="scr2.js"></script> <script> console.log("Inline script:"); console.log(text); </script> </head> <body> <div id="target1"></div> <div id="target2"></div> <div id="target3"></div> </body> </html>
Файл scr1.js
var text = "Text from scr1.js"; console.log(text);
Файл scr2.js
var text = "Text from scr2.js"; console.log(text);
Первым импортируется файл scr1.js
, вторым — scr2.js
, поэтому вложенный сценарий, объявленный после импорта двух внешних, получит переменную text
со значением, которое присвоено в scr2.js
.
TypeScript устраняет этот недостаток за счет пространств имен. Термин «пространство имен» введен в TypeScript 1.5. До этой версии в TypeScript были внутренние и внешние модули. Теперь внутренние модули называются пространствами имен, а внешние — просто модулями.
Для определения пространства имен используется ключевое слово namespace. Классы и интерфейсы, к которым будут обращаться извне модуля, необходимо пометить ключевым словом export
.
namespace MyNameSpaceName { export MyClassName { } export IMyInterfaceName { } }
Для доступа к классу или интерфейсу, которые находятся в другом пространстве имен, используется запись с точкой: namespaceName.className
.
Если пространство имен находится в другом файле, то необходимо указать ссылку на этот файл, используя запись с тремя косыми чертами:
/// <reference path = "SomeFileName.ts" />
Объявим переменные с одним и тем же именем в разных пространствах имен.
Файл animals.ts
namespace Animals { export let count = 12; }
Файл people.ts
namespace People { export let count = 3; }
Файл howmany.ts
/// <reference path = "./people.ts" /> /// <reference path = "./animals.ts" /> console.log(People.count); console.log(Animals.count);
Скомпилируем эти файлы в код JavaScript. Для этого передадим команде tsc
параметр --outFile
, за которым указывается имя целевого файла JavaScript, а затем перечисляются файлы TypeScript.
npx tsc --outFile howmany.js howmany.ts animals.ts people.ts
Получим следующий код JavaScript:
var People; (function (People) { People.count = 3; })(People || (People = {})); var Animals; (function (Animals) { Animals.count = 12; })(Animals || (Animals = {})); /// <reference path = "./people.ts" /> /// <reference path = "./animals.ts" /> console.log(People.count); console.log(Animals.count);
Соответственно, результаты вывода значения count
будут разными: 3 и 12.
Data
, в которое вложено пространство имен Animal
с классом Dog
, то псевдоним можно объявить так: import dog = Data.Animal.Dog
. Тогда можно будет обращаться к полям и методам этого класса через префикс dog
.Модули помогают упорядочить код, написанный на TypeScript. До TypeScript 1.5 модули делились на внутренние и внешние. С версии 1.5 внутренние модули называются пространствами имен, а внешние — собственно модулями.
Модулем считается любой файл с импортом или экспортом на высшем уровне. Модули выполняются в собственной области видимости.
Файл без импорта и экспорта на высшем уровне считается сценарием, чье содержимое доступно в глобальной области видимости.
Чтобы интерфейс, класс, переменную или функцию можно было использовать в другом файле, перед объявлением добавляется ключевое слово export
(как это делалось в примере с пространствами имен). Но вместо того, чтобы добавлять его к каждому объявлению, можно использовать более простую и наглядную запись, объединяя классы, интерфейсы и т. п.:
export { Dog, move };
Модуль импортируется с помощью ключевого слова import
:
import { Dog, move } from "./myfile.js";
При импорте можно указывать псевдонимы с помощью ключевого слова as
(его также можно использовать для создания псевдонима при экспорте):
import { Dog, move as myMove } from "./myfile.js";
Можно импортировать модуль целиком в одно пространство имен:
import * as mymodule from "./myfile.js";
В конце концов, можно указать один класс как экспортируемый по умолчанию. Для этого после слова export
указывается слово default
. При этом данный класс будет импортироваться по умолчанию, даже если в операторе импорта указан псевдоним.
JavaScript — это подмножество TypeScript. Казалось бы, если в сценарии TS есть код JS, то компилятор TypeScript примет его без проблем. Это справедливо, если код JavaScript написан с учетом безопасности с точки зрения типов.
Другое дело, если вы используете большие библиотеки JS или сторонние, например jQuery, Node.js или AngularJS (именно AngularJS, потому что библиотека Angular написана на TypeScript). Чтобы обеспечить безопасность типов и ввод с автодополнением (intellisense
), TypeScript-программисту придется приложить нечеловеческие усилия.
Объявления окружения (ambient
) помогают беспроблемно встраивать библиотеки JS в TypeScript. Определения окружений по соглашению помещаются в файл определения типов в расширением d.ts
, например ThirdPartyLib.d.ts
. Этот файл не компилируется в JavaScript.
Переменные и модули определяются так:
declare module Module_Name { export class SomeClass{ doSmth(times:number) : number; } }
В файле окружения нет реализации. В нем объявляются типы. Затем они включаются в файл TypeScript следующим образом:
/// <reference path = "ThirdPartyLib.d.ts" />
В результате при попытке передать функции doSmth
строковое значение будет выдано сообщение об ошибке — еще во время компиляции.
Таким образом можно состыковывать сторонние библиотеки с кодом TypeScript и не беспокоиться о том, что код поведет себя непредсказуемо.
Создадим приложение для предварительного просмотра сообщений. Сообщения будут делиться на три вида: информационные, предупреждения и сообщения об ошибке. В зависимости от выбранного типа сообщения меняется его стиль.
Создадим файл с полями для указания заголовка, текста и типа сообщения, а также с родительским элементом для его просмотра. Просмотр измененного сообщения будет доступен по нажатию кнопки.
Файл msg.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Сообщения</title> <link rel="stylesheet" type="text/css" href="style.css"> <script type="text/javascript" src="script.js"></script> </head> <body> <h1>Сообщения</h1> <div class="param"> <div> <p>Заголовок</p> <input type="text" id="header" value="Заголовок"> </div> <div> <p>Текст</p> <textarea id="body" rows="10">Текст сообщения</textarea> </div> <div> <p>Тип</p> <select id="type"> <option value="info">Сообщение</option> <option value="warning">Предупреждение</option> <option value="error">Ошибка</option> </select> </div> <button >Настроим внешний вид элементов. Стиль элементов будет состоять из двух частей: типа (
error|warning|info
) и назначения: заголовка и тела сообщения.Файл style.css
body { font-family: Verdana, sans-serif; color: #336699; } div { margin: 5px; padding: 5px; } .param { width:300px; display: inline-block; float: left; background-color: #336699; color: #ffffff; border-radius: 10px; } .result { display: inline-block; float: left; width: 300px; background-color: #ffffff; color: #336699; border: 1px solid #003366; border-radius: 10px; } .message { border: 1px solid; border-radius: 10px; padding: 0px; } .header { font-weight: bold; font-size: larger; } .body { font-style: italic; } .info { color: #009900; border-color: #009900; } .warning { color: #FF8C00; border-color: #FF8C00; } .error { color: #990000; border-color: #990000; }По нажатию кнопки будем менять весь стиль элемента разом. Поскольку из скрипта можно менять только его отдельные свойства, заменим весь
innerHTML
элементаresult
. Для каждого элемента укажем класс, подставив литеральный тип перед идентификатором класса назначения элемента.Файл script.ts
// Типы сообщений помещаем в объединение type msgTypes = "info" | "warning" | "error"; // Части сообщения тоже помещаем в объединение type msgParts = "header" | "body"; class Message { header: string; body: string; type: msgTypes; constructor(header: string, body: string, type: msgTypes) { this.header = header; this.body = body; this.type = type; } insert(resultElement: HTMLElement) : void { // Поскольку тип определен как строковой литерал, // включаем его в строку без проблем resultElement.innerHTML = `<div class="${this.type} message" id="message"> <div class="${this.type} header">${this.header}</div> <div class="${this.type} body">${this.body}</div> </div>`; } } function updateMessage() { // Получаем значения элементов в соответствии с типами const header = (document.getElementById("header") as HTMLInputElement).value; const body = (document.getElementById("body") as HTMLInputElement).value; const type = (document.getElementById("type") as HTMLSelectElement).value; // Настраиваем сообщение let msg = new Message(header, body, type as msgTypes); msg.insert(document.getElementById("result")); }Это приложение не столько полезно с точки зрения функциональности, сколько наглядно с точки зрения знакомства с некоторыми рассмотренными здесь возможностями TypeScript: типами, объединениями, шаблонами литералов типов.
После компиляции получим такой код:
Файл script.js
var Message = /** @class */ (function () { function Message(header, body, type) { this.header = header; this.body = body; this.type = type; } Message.prototype.insert = function (resultElement) { resultElement.innerHTML = "<div class=\"".concat(this.type, " message\" id=\"message\">\n <div class=\"").concat(this.type, " header\">").concat(this.header, "</div>\n <div class=\"").concat(this.type, " body\">").concat(this.body, "</div>\n</div>"); }; return Message; }()); function updateMessage() { // Получаем значение элементов в соответствии с типами var header = document.getElementById("header").value; var body = document.getElementById("body").value; var type = document.getElementById("type").value; // Настраиваем сообщение var msg = new Message(header, body, type); msg.insert(document.getElementById("result")); }7. Преимущества и недостатки TypeScript
Преимущества TypeScript составляют, среди прочих, его следующие особенности:
- возможность выявлять ошибки еще в редакторе кода и во время компиляции;
- поддержка ООП;
- удобная система типов;
- пригодность для разработки крупных проектов.
Недостатки TypeScript, на наш взгляд, таковы:
- снижает скорость разработки за счет необходимости указания типов;
- компиляция существенно замедляет сборку, особенно если речь идет о больших проектах;
- необходимость самостоятельно прописывать сигнатуры для внешних библиотек;
- порог входа повышен: в TypeScript столько нововведений, что его освоение можно сравнить с изучением нового языка.
Итоги
TypeScript позволяет избегать ошибок уже в начале разработки проекта, что избавляет от непредвиденных затрат. TypeScript можно представить себе как безопасное расширение, своего рода апгрейд для старого-доброго Javascript, программирование в котором давно уже имеет довольно недобрую репутацию.
В TypeScript реализовано много интересных и полезных возможностей. Поэтому над ним придется помучиться, зато вы освоите язык, популярность которого растет: в 2021 году он обогнал C++ и занял 6-е место среди языков программирования, используемых в работе.
В любом случае знакомство с этим языком идет на пользу, и если вдруг появится возможность работать над крупным фронтенд-проектом на TypeScript, вы будете заранее готовы.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…