Интересно, а почему бы просто не вести разработку на JavaScript? Зачем использовать другие языки программирования, если уже есть язык JavaScript? Вам ведь не попадались статьи о языках для разработки приложений Java или C#?
Дело в том, что разработка на JavaScript не отличается высокой продуктивностью. Предположим, функция ожидает в качестве аргумента строковое значение, но разработчик ошибочно вызывает ее с передачей числового значения. При работе с JavaScript эта ошибка может быть обнаружена только в ходе выполнения сценария. Компиляторы Java или C# не станут даже компилировать код, в котором имеется несоответствие типов, но интерпретатор JavaScript такой код пропускает, поскольку относится к динамически типизированным языкам.
Несмотря на то что движки JavaScript проделывают вполне достойную уважения работу, выстраивая догадки о типах переменных по их значениям, средства разработки располагают весьма ограниченными возможностями по оказанию помощи, ничего не зная о типах. В приложениях, имеющих средний и большой объем кода, этот недостаток JavaScript снижает продуктивность труда разработчиков программных продуктов.
Для более крупных проектов особую важность приобретают качественная контекстно зависимая помощь интегрированной среды разработки (IDE) и поддержка реструктуризации. Переименование во всех местах появлений той или иной переменной или функции в статически типизированных языках выполняется IDE-средами в доли секунды, даже в проектах, состоящих из тысяч строк кода, но сказать такое же о JavaScript, языке, не поддерживающем типы, невозможно. IDE-среды могут помочь с реструктуризацией гораздо эффективнее, когда известны типы переменных.
Чтобы повысить продуктивность работы, можно рассмотреть возможность ведения разработки в статически типизированных языках с последующей конвертацией кода в JavaScript для развертывания приложения. В настоящее время существует дюжина языков, компилируемых в JavaScript (их перечень можно найти на GitHub (). Наиболее популярными из них являются TypeScript (), CoffeeScript () и Dart ().
А почему бы не воспользоваться языком DART? Мы уделили работе с языком Dart немало времени, и он нам понравился, но у него все же есть ряд недостатков: степень взаимодействия с JavaScript-библиотеками сторонних разработчиков невелика; вести разработку на Dart можно только на специализированных версиях браузера Chrome (Dartium), поставляемого вместе с виртуальной машиной Dart VM. Другие браузеры такой возможности не предоставляют; код, создаваемый JavaScript, человеку читать сложно; сообщество разработчиков, пользующихся языком Dart, сравнительно мало. |
Фреймворк Angular написан на TypeScript, и в данном приложении будет рассмотрен синтаксис именно этого языка. Все примеры кода, приводимые в данной книге, написаны на TypeScript. Кроме того, мы показали вам, как превратить код TypeScript в его JavaScript-версию, чтобы он мог выполняться любым браузером или автономным движком JavaScript.
Можно создавать приложения на ES6 (и даже на ES5), но мы воспользовались TypeScript как значительно более продуктивным способом написания кода JavaScript, и вот почему.
• TypeScript поддерживает типы. Он позволяет компилятору TypeScript помогать вам искать и устранять множество ошибок в ходе разработки, даже перед запуском приложения.
• Одним из основных преимуществ TypeScript является хорошая поддержка IDE-среды. При ошибке, допущенной в имени функции или переменной, код отображается красным цветом. При передаче функции неверного количества параметров (или неверных типов) все, что не подходит, показывается красным. IDE-среды также предоставляют великолепную контекстно зависимую помощь. Код TypeScript может быть реструктурирован IDE-средами, а код JavaScript должен реструктурироваться вручную. Если нужно исследовать новую библиотеку, то следует просто установить ее файл определения типов, и IDE-среда проинформирует о наличии доступных API, избавляя от необходимости читать ее документацию из какого-либо другого источника.
• В пакет Angular входят файлы определения типов, поэтому IDE-среды проверяют соответствие типов при использовании API Angular и тут же предлагают контекстно зависимую помощь.
• TypeScript следует положениями спецификаций ECMAScript 6 и 7 и добавляет к ним типы, интерфейсы, декораторы, переменные элементов класса (поля), обобщения и ключевые слова public и private. Предстоящие выпуски TypeScript будут поддерживать отсутствующие функции ES6 и реализовывать функции ES7 (см. «Дорожную карту» TypeScript на GitHub на ).
• Интерфейсы TypeScript позволяют объявлять пользовательские типы, используемые в вашем приложении. Интерфейсы помогают избегать ошибки в ходе компиляции, вызванные применением в вашем приложении объектов неверных типов.
• Созданный код JavaScript легко читается и выглядит как код, написанный вручную.
• Большинство примеров кода в документации по Angular, статьях и блогах дается в TypeScript (см. ).
Браузеры понимают только язык JavaScript. В случае если исходный код написан на TypeScript, прежде чем запускать его в браузере или автономном движке JavaScript, нужно его транспилировать в JavaScript.
Транспиляция означает преобразование исходного кода программы на одном из языков в исходный код программы на другом языке. Многие разработчики отдают предпочтение слову «компиляция», поэтому такие фразы, как «компилятор TypeScript» и «компиляция TypeScript в JavaScript», тоже имеют право на существование.
На рис. Б.1 показан снимок экрана с кодом TypeScript слева и его эквивалентом в ES5-версии JavaScript, созданным транспилятором TypeScript. В TypeScript объявляется переменная foo типа string, а в транспилированной версии информация о типе отсутствует. В TypeScript объявляется класс Bar, который был транспилирован в похожий на класс шаблон в синтаксисе ES5. Если в качестве цели транспиляции был бы указан ES6, то созданный код JavaScript выглядел бы иначе.
Рис. Б.1. Транспиляция TypeScript в ES5
Сочетание Angular со статически типизированным TypeScript упрощает разработку веб-приложений среднего и большого объема. Качественное инструментальное оснащение и анализатор статических типов существенно сокращают количество ошибок, выявляемых в ходе выполнения программы, и уменьшают время вывода программного продукта на рынок. Когда работа над проектом завершится, в вашем Angular-приложении будет большой объем кода на JavaScript. И хотя разработка на TypeScript потребует написания большего объема кода, вы станете пожинать плоды за счет экономии времени на тестировании и реорганизации, а также на сведении к минимуму количества ошибок, выявляемых при выполнении программы.
У компании Microsoft имеется TypeScript с открытым кодом, и она разместила репозиторий TypeScript на GitHub (). Компилятор TypeScript можно установить, используя npm или загрузив его с . На сайте TypeScript также имеется встроенный (интерактивный) компилятор TypeScript, в который можно вводить код TypeScript и компилировать его в код JavaScript в интерактивном режиме, как показано на рис. Б.2.
Рис. Б.2. Применение интерактивной среды TypeScript
В интерактивной среде TypeScript код на этом языке вводится в левой части окна, а его JavaScript-версия появляется в его правой части. Для запуска транспилированного кода нужно нажать кнопку Run (Запуск) (консольный вывод, произведенный вашим кодом, если таковой имеется, будет доступен для просмотра после открытия в браузере панели Developer Tools (Инструменты разработчика)).
Интерактивного средства будет достаточно, чтобы изучить синтаксис языка, но для настоящей продуктивной разработки нужна более подходящая оснастка. Можно остановиться на использовании IDE-среды или текстового редактора, но для разработки будет не обойтись без локально установленного компилятора TypeScript.
Сам компилятор TypeScript написан на TypeScript. Для его установки нужно воспользоваться имеющимся в Node.js диспетчером пакетов npm. Если Node отсутствует, то его нужно загрузить с / и установить на компьютер. Система Node.js поставляется с npm, который будет применен для установки не только компилятора TypeScript, но и многих других средств разработки, упоминаемых в данной книге.
Для глобальной установки компилятора TypeScript нужно запустить в окне команд или терминала следующую npm-команду:
npm install -g typescript
Ключ -g приведет к глобальной установке компилятора TypeScript на вашем компьютере, и он станет доступен из приглашения командной строки всем вашим проектам. Для разработки приложений Angular загрузите самую последнюю версию компилятора TypeScript (при написании этой книги использовалась версия 2.0).
Для проверки версии вашего компилятора TypeScript запустите следующую команду:
tsc --version
Созданный на TypeScript код нужно транспилировать в JavaScript, чтобы браузер мог его выполнить. Код на TypeScript сохраняется в файлах с расширением .ts. Предположим, вы написали сценарий, который сохранен в файле main.ts. Тогда следующая команда приведет к транспиляции main.ts в main.js:
tsc main.ts
Можно также создавать файлы отображения исходного кода, проецирующие исходный код TypeScript на созданный код JavaScript. Имея эти отображения, можно устанавливать контрольные точки в вашем коде TypeScript в момент запуска сценария в браузере, даже притом, что последний будет выполнять код JavaScript. Для компиляции main.ts в main.js с попутным созданием файла отображения исходного кода main.map нужно запустить следующую команду:
tsc --sourcemap main.ts
На рис. Б.3 показан снимок экрана, полученный при отладке, проводимой на панели Developer Tools (Инструменты разработчика) браузера Chrome. Обратите внимание на контрольную точку в строке 15. На вкладке Sources (Исходные коды) этой панели можно найти ваш файл TypeScript, поместить в коде контрольную точку и отслеживать значения переменных в правой стороне экрана.
Рис. Б.3. Отладка TypeScript на панели Developer Tools (Инструменты разработчика)
Чтобы получился корректный код JavaScript, в ходе компиляции компилятор TypeScript удаляет из создаваемого кода все типы TypeScript, интерфейсы и ключевые слова. Предоставляя компилятору те или иные ключи, можно создавать код JavaScript, совместимый с синтаксисом ES3, ES5 или ES6. В настоящий момент по умолчанию создается код, совместимый с ES3. А вот как транспилировать код в синтаксис, совместимый с ES5:
tsc --t ES5 main.ts
Транспиляция TypeScript в браузере В ходе разработки мы задействуем локально установленный компилятор tsc, но транспиляцию можно также выполнить либо на сервере в ходе разработки, либо динамически, когда браузер загружает ваше приложение. При написании книги мы применяли библиотеку SystemJS, внутри которой для транспиляции и динамической загрузки модулей приложения используется tsc. Имейте в виду, что динамическая транспиляция в браузере может приводить к задержкам в отображении содержимого вашего приложения на устройствах пользователя. Если для загрузки и транспиляции вашего кода в браузер используется SystemJS, то отображения исходного кода будут создаваться по умолчанию. |
При необходимости откомпилировать код в памяти без создания выходных файлов с расширением .js компилятор tsc нужно запускать с ключом --noEmit. Мы часто задействуем этот ключ в режиме разработки, поскольку нам нужен только выполняемый код JavaScript в памяти браузера.
Компилятор TypeScript можно запускать в режиме отслеживания, предоставив для этого ключ -w. В данном режиме любое изменение и сохранение вашего кода будет приводить к его автоматической транспиляции в соответствующие файлы JavaScript. Чтобы скомпилировать и отслеживать все файлы с расширением .ts, запустите следующую команду:
tsc -w *.ts
Компилятор скомпилирует все файлы TypeScript, выведет сообщения об ошибках (если таковые обнаружатся) на консоль и продолжит отслеживать изменения в файлах. Как только файл будет изменен, tsc тут же его перекомпилирует.
ПРИМЕЧАНИЕ
Обычно мы не используем среду IDE для компиляции TypeScript. Задействуется либо SystemJS с компилятором, работающим в среде браузера, либо упаковщик (Webpack), применяющий для компиляции специальный загрузчик TypeScript. Мы используем анализатор кода TypeScript, предоставляемый IDE-средами для выделения ошибок, и браузер для отладки TypeScript.
Компилятор TypeScript позволяет заранее сконфигурировать процесс компиляции (указать каталоги источника и получателя, создать отображения исходного кода и т.д.). Присутствие в каталоге проекта конфигурационного файла tsconfig.json означает следующее: ввод tsc приведет к тому, что компилятор считает все ключи из этого файла. Пример файла tsconfig.json показан в листинге Б.1.
Листинг Б.1. Содержимое файла tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"rootDir": ".",
"outDir": "./js"
}
}
Этот файл конфигурации настраивает tsc на транспилирование кода в синтаксис ES5. Создаваемые файлы JavaScript будут размещены в каталоге js. Файл tsconfig.json может включать раздел files, в котором перечисляются файлы, подлежащие компиляции TypeScript. В листинг Б.1 данный перечень не включен, поскольку в нем используется ключ rootDir для запроса компиляции всех файлов, начиная с корневого каталога проекта.
Если необходимо исключить некоторые файлы проекта из компиляции, то к tsconfig.json следует добавить свойство exclude. Исключить все содержимое каталога node_modules можно следующим образом:
"exclude": [
"node_modules"
]
Дополнительные сведения о конфигурировании процесса компиляции и ключах компилятора TypeScript можно найти в документации TypeScript ().
ПРИМЕЧАНИЕ
В большинстве примеров Angular, приведенных в данной книге, с классами или элементами классов используются аннотации (также называемые декораторами), например, @Component и @Input. Аннотации являются способом добавления метаданных к аннотируемым классам или их элементам. Подробности можно найти во врезке «Что такое метаданные» в подразделе 2.1.1.
В TypeScript полностью поддерживаются ES5 и основная часть синтаксиса ES6. Можно просто изменить расширение файла с кодом JavaScript с .js на .ts, и он станет файлом с вполне допустимым кодом TypeScript. До сих пор нам попадались только два исключения, касающиеся обработки необязательных параметров функций и присваивания значения литералу объекта.
В JavaScript, даже если функция объявлена с двумя параметрами, ее можно вызвать, предоставив только один параметр, а в TypeScript, чтобы сделать параметр необязательным, к его имени нужно добавить знак вопроса. В JavaScript можно инициализировать переменную, указав пустой литерал объекта, и тут же прикрепить свойство, используя запись через точку, а в TypeScript придется задействовать квадратные скобки.
Но эти различия минимальны. Гораздо важнее то, что, являясь расширенной версией JavaScript, TypeScript добавляет к JavaScript ряд весьма полезных функций, который мы и рассмотрим далее.
СОВЕТ
Если вы находитесь на полпути преобразования проекта JavaScript в TypeScript, то можете воспользоваться имеющимся в компиляторе tsc ключом --allowJs. Компилятор TypeScript проверит входные файлы с расширением .js на синтаксические ошибки и выдаст допустимый результат, основываясь на ключах tsc --target и --module. Его можно объединить с другими файлами с расширением .ts. Отображения исходного кода по-прежнему создаются для файлов .js, точно так же, как и для файлов .ts.
Можно объявлять переменные и предоставлять типы для всех этих переменных или только для некоторых из них. Синтаксис TypeScript допускает обе строки кода, представленные ниже:
var name1 = 'John Smith';
var name2: string = 'John Smith';
При использовании типов транспилятор TypeScript может обнаружить несоответствие типов в ходе разработки, а IDE-среды станут предлагать варианты завершения кода и поддержку реструктуризации. Это повысит производительность вашей работы над любым достаточно большим по объему проектом. Даже если не применять типы в объявлениях, TypeScript сам выведет тип на основе присвоенного значения и потом все равно станет проверять соответствие типов. Данная особенность называется выведением типа.
В следующем фрагменте кода TypeScript показана невозможность присвоить числовое значение переменной name1, которая должна была быть строковой переменной, даже притом, что изначально она объявлена без указания типа (с помощью синтаксиса JavaScript). После инициализации этой переменной строковым значением выведение типов не позволит присвоить name1 числовое значение. Те же правила применимы и к переменной name2, объявленной с явным указанием типа:
В TypeScript допускается объявление типизированных переменных, параметров функций и возвращаемых значений. Для объявления основных типов используются четыре ключевых слова: number, boolean, string и void. Последнее показывает в объявлении функции отсутствие возвращаемого значения. Как и в JavaScript, переменной может быть значение типа null или undefined.
Рассмотрим некоторые примеры переменных, объявленных с явным указанием типов:
var salary: number;
var name: string = "Alex";
var isValid: boolean;
var customerName: string = null;
Все эти типы являются подтипами типа any. Если при объявлении переменной или аргумента функции тип не указан, то компилятор TypeScript сделает предположение, что у него имеется тип any; это позволит присвоить данной переменной или аргументу функции любое значение.
Можно также воспользоваться явным объявлением переменной с указанием any в качестве ее типа. В таком случае выведение типа применено не будет. Допустимы оба следующих объявления:
var name2: any = 'John Smith';
name2 = 123;
Если переменные объявлены с явным указанием типов, то компилятор проверит их значения, чтобы убедиться в их соответствии объявлениям. В TypeScript включены и другие типы, используемые при взаимодействии с браузером, например HTMLElement и Document.
Если вы определяете класс или интерфейс, то можете применять его в объявлениях переменных в качестве пользовательского типа. К классам и интерфейсам мы еще вернемся, но сначала познакомимся с функциями TypeScript, являющимися в JavaScript наиболее востребованными конструкциями.
Функции TypeScript (и функциональные выражения) похожи на функции JavaScript, но при этом имеется возможность явного объявления типов параметров и возвращаемых значений. Напишем функцию JavaScript, вычисляющую размер налога (листинг Б.2). У нее будет три параметра, и она станет вычислять налог на основе штата проживания, суммы дохода и количества иждивенцев. Для каждого иждивенца в зависимости от штата проживания имеется право на вычет из налогооблагаемой суммы в размере $500 или 300.
Листинг Б.2. Вычисление суммы налога с помощью JavaScript
function calcTax(state, income, dependents) {
if (state == 'NY') {
return income * 0.06 - dependents * 500;
} else if (state == 'NJ') {
return income * 0.05 - dependents * 300;
}
}
Предположим, что налогоплательщик с доходом $50 000 живет в штате Нью-Джерси и имеет на иждивении двух человек. Вызовем calcTax():
var tax = calcTax('NJ', 50000, 2);
Переменная tax получает значение 1900, не вызывающее возражений. Даже притом, что в calcTax() для параметров функции не объявляются никакие типы, их можно вывести на основе имен параметров.
Теперь вызовем функцию неподобающим образом, передав для количества иждивенцев строковое значение:
var tax = calcTax('NJ', 50000, 'two');
Выявить проблему до вызова этой функции невозможно. Переменная tax будет иметь значение NaN (не число). Ошибка вкралась только по причине невозможности явного указания типов параметров. Перепишем данную функцию на TypeScript, объявив типы для параметров и возвращаемого значения (листинг Б.3).
Листинг Б.3. Вычисление суммы налога с помощью TypeScript
function calcTax(state: string, income: number, dependents: number): number{
if (state == 'NY'){
return income*0.06 - dependents*500;
} else if (state=='NJ'){
return income*0.05 - dependents*300;
}
}
Теперь допустить такую же ошибку и передать строковое значение для количества иждивенцев невозможно:
var tax: number = calcTax('NJ', 50000, 'two');
Компилятор TypeScript выдаст ошибку со следующим сообщением: Argument of type 'string' is not assignable to parameter of type 'number'. Более того, для возвращаемого значения функции объявлен тип number, который не позволит допустить еще одну ошибку и присвоить результат вычисления налога нечисловой переменной:
var tax: string = calcTax('NJ', 50000, 'two');
Компилятор выявит эту ошибку и выдаст сообщение: The type 'number' is not assignable to type 'string': var tax: string. Такая проверка соответствия типов в ходе компиляции позволит вам сэкономить уйму времени при разработке любого проекта.
При объявлении функции можно указать значения параметров по умолчанию. Единственным ограничением является то, что параметры со значениями по умолчанию не могут иметь после себя обязательные параметры. В листинге Б.3, чтобы предоставить NY в качестве значения по умолчанию для параметра state, объявить это следующим образом невозможно:
function calcTax(state: string = 'NY', income: number, dependents: number):
number{
// сюда помещается код }
Чтобы гарантировать отсутствие обязательных параметров после параметра по умолчанию, нужно изменить порядок следования параметров:
function calcTax(income: number, dependents: number, state: string = 'NY'):
number{
// сюда помещается код
}
В теле calcTax() не нужно изменять ни одной строки кода. Теперь можно совершенно свободно вызвать эту функцию либо с двумя, либо с тремя параметрами:
var tax: number = calcTax(50000, 2);
// или
var tax: number = calcTax(50000, 2, 'NY');
Результат обоих вызовов будет одинаковым.
В TypeScript можно просто пометить параметры функции как необязательные, добавив к имени параметра знак вопроса. Единственным ограничением является то, что дополнительные параметры должны указываться в объявлении функции последними. При написании кода для функций с необязательными параметрами необходимо реализовать логику приложения, обрабатывающую те случаи, когда необязательные параметры не предоставлены.
Изменим функцию вычисления суммы налога: если количество иждивенцев не указано, то вычеты к вычислению налога применяться не будут (листинг Б.4).
Листинг Б.4. Измененное вычисление суммы налога с помощью TypeScript
Обратите внимание на знак вопроса в dependents?: number. Теперь функция проверяет, предоставлено ли значение для количества иждивенцев. Если нет, то переменной deduction присваивается значение 0, в противном случае из каждого иждивенца вычитается 500.
Запуск кода листинга Б.4 выдаст следующий результат:
Your tax is 1000
Your tax is 3000
В TypeScript поддерживается упрощенный синтаксис использования в выражениях безымянных функций. При этом исключается необходимость применения ключевого слова function, и для отделения параметров функции от ее тела служит знак жирной стрелки (=>). В TypeScript для стрелочных функций поддерживается синтаксис ES6 (более подробно данные функции рассмотрены в приложении А). В некоторых других языках программирования стрелочные функции известны как лямбда-выражения.
Рассмотрим простейший пример стрелочной функции с телом, умещающимся в одной строке:
var getName = () => 'John Smith';
console.log(getName());
Пустые круглые скобки обозначают отсутствие в предшествующей стрелочной функции параметров. Для стрелочного выражения, умещающегося в одной строке, не нужны ни фигурные скобки, ни явно указываемая инструкция return, и предыдущий фрагмент кода выведет в консоли John Smith. Если ввести этот код в интерактивную среду TypeScript, то она преобразует его в следующий код ES5:
var getName = function () { return 'John Smith'; };
console.log(getName());
Если тело стрелочной функции состоит из нескольких строк, то его придется заключить в фигурные скобки и воспользоваться инструкцией return. В следующем фрагменте кода происходит преобразование конкретно заданного строкового значения в строку с символами в верхнем регистре, и в консоли выводится PETER LUGER:
var getNameUpper = () => {
var name = 'Peter Luger'.toUpperCase();
return name;
}
console.log(getNameUpper());
Кроме предоставления более лаконичного синтаксиса, выражения стрелочных функций устраняют небезызвестную путаницу с ключевым словом this. Если в JavaScript оно используется в функции, то может не указывать на объект, из которого была вызвана данная функция. Это может привести к ошибкам в ходе выполнения и потребовать дополнительного времени на отладку. Рассмотрим пример.
В листинге Б.5 имеется две функции: StockQuoteGeneratorArrow() и StockQuoteGeneratorAnonymous(). Каждую секунду они обе вызывают Math.random() для выдачи произвольной цены на акцию, символ которой предоставлен в качестве параметра. Внутри StockQuoteGeneratorArrow() используется синтаксис стрелочной функции, предоставляющей аргумент для setInterval(), а внутри StockQuoteGeneratorAnonymous() применяется безымянная функция.
Листинг Б.5. Использование выражения стрелочной функции
В обоих случаях символ акции (IBM) присваивается переменной symbol объекта this, но при использовании стрелочной функции ссылка на экземпляр функции-конструктора StockQuoteGeneratorArrow() автоматически сохраняется в отдельной переменной. При ссылке на this.symbol из стрелочной функции она правильно находит эту переменную и задействует в выводе в консоли IBM.
Но когда в браузере вызывается безымянная функция, this указывает на глобальный объект Window, у которого нет свойства symbol. Запуск этого кода в браузере приведет к выводу каждую секунду информации, подобной представленной ниже:
StockQuoteGeneratorArrow. The price quote for IBM is 0.2998261866159737
StockQuoteGeneratorAnonymous.The price quote for undefined is
0.9333276399411261
Как видите, при использовании стрелочной функции она распознает IBM в качестве символа акции, а в безымянной функции получается неопределенность (undefined).
ПРИМЕЧАНИЕ
TypeScript заменяет this в выражении стрелочной функции ссылкой на this из внешней области видимости путем передачи в ссылке. Дело в том, что код в стрелочной функции в StockQuoteGeneratorArrow() правильно видит this.symbol из внешней области видимости.
Нашей следующей темой будут классы TypeScript, но сделаем небольшую паузу и подведем итоги всему только что рассмотренному:
• код Typescript компилируется в JavaScript с помощью компилятора tsc;
• TypeScript позволяет объявлять типы переменных, параметров функций и возвращаемых значений;
• функции могут иметь параметры со значениями по умолчанию, а также необязательные параметры;
• выражения стрелочных функций предлагают более лаконичный синтаксис для объявления безымянных функций;
• выражения стрелочных функций устраняют неопределенность в использовании ссылки на объект this.
Перегрузка функций Функция перегрузки в JavaScript не поддерживается, поэтому иметь несколько функций с одним и тем же именем, но с разными списками аргументов невозможно. Создатели TypeScript ввели функцию перегрузки, но поскольку код должен быть транспилирован в одну функцию JavaScript, синтаксис для перегрузки выглядит совсем не элегантно. Можно объявить несколько сигнатур функции с одним и только одним телом, где нужно будет проверять количество и типы аргументов и выполнять соответствующие части кода: function attr(name: string): string; function attr(name: string, value: string): void; function attr(map: any): void; function attr(nameOrMap: any, value?: string): any { if (nameOrMap && typeof nameOrMap === "string") { // обработка варианта со строкой } else { // обработка варианта с отображением } // здесь проводится обработка значения } |
Тем, у кого есть опыт программирования на Java или на C#, должно быть знакомо понятие классов и наследования в их классическом виде. В таких языках определение класса загружается в память в качестве отдельной сущности (вроде общего замысла) и совместно используется всеми экземплярами этого класса. Если класс является наследником другого класса, то объект, создаваемый в качестве его экземпляра, задействует комбинированный общий замысел обоих классов.
TypeScript — расширенная версия языка JavaScript, в котором поддерживается только прототипное наследование, позволяющее создавать иерархию наследования путем прикрепления одного объекта к свойству prototype другого объекта. В данном случае наследование объектов (или скорее их связь) создается динамически.
В TypeScript ключевое слово class является синтаксической уловкой, упрощающей программирование. В конечном счете ваши классы будут транспилированы в объекты JavaScript с прототипным наследованием. В JavaScript допускается объявление функции-конструктора и создание ее экземпляра с помощью ключевого слова new. В TypeScript можно также объявить класс и создать его экземпляр, используя оператор new.
В класс можно включать конструктор, поля (они же свойства) и методы. Объявленные свойства и методы часто называют элементами класса. Мы проиллюстрируем синтаксис классов TypeScript, показав ряд примеров и сравнивая их с соответствующими синтаксическими конструкциями в ES5.
Создадим простой класс Person, содержащий четыре свойства для хранения фамилии, имени, возраста и номера карточки социального страхования (уникального идентификатора, присваиваемого лицу, легально проживающему в Соединенных Штатах). В левой части рис. Б.4 можно увидеть код TypeScript, содержащий объявление и создающий экземпляр класса Person, а в его правой части — функцию-замыкание на JavaScript, созданную компилятором tsc. Создавая замыкание для функции Person, компилятор TypeScript обеспечивает механизм для экспонирования и скрытия элементов объекта Person.
Рис. Б.4. Транспиляция класса TypeScript в замыкание JavaScript
В TypeScript также поддерживаются конструкторы класса, позволяющие инициализировать переменные объекта при создании его экземпляра. Конструктор класса вызывается только один раз в ходе создания объекта. В левой части рис. Б.5 показана еще одна версия класса Person, в которой используется ключевое слово constructor, позволяющее проинициализировать поля класса значениями, переданными конструктору. В правой части рисунка показана созданная ES5-версия кода.
Рис. Б.5. Транспиляция имеющегося в TypeScript класса с конструктором
Некоторые разработчики программных средств на JavaScript могут посчитать, что от использования классов мало проку, поскольку они легко смогут запрограммировать те же самые функциональные свойства с помощью функций-конструкторов и замыканий. Но те, кто только что приступил к работе с JavaScript, сочтут синтаксис классов более легким для чтения и написания, по сравнению с функциями-конструкторами и замыканиями.
В JavaScript невозможно объявить переменную или метод закрытыми (спрятанными от внешнего кода). Для скрытия свойства (или метода) в объекте нужно создать замыкание, которое не будет ни прикреплять это свойство к переменной this, ни возвращать его в инструкции return замыкания.
TypeScript предоставляет ключевые слова public, protected и private, чтобы помочь управлять доступом к элементам объектов в ходе разработки. По умолчанию все элементы класса находятся в открытом доступе (public) и видимы за пределами класса. Если элемент объявлен с модификатором protected (защищенный), то он видим в классе и его подклассах. Элемент класса, объявленный с модификатором private (закрытый), видим только в классе.
Применим ключевое слово private в целях скрыть значение свойства ssn, чтобы исключить к нему непосредственный доступ за пределами объекта Person. Мы покажем две версии объявления класса со свойствами, в которых задействованы модификаторы доступа. Более длинная версия класса имеет следующий вид (листинг Б.6).
Листинг Б.6. Использование закрытого свойства
class Person {
public firstName: string;
public lastName: string;
public age: number;
private _ssn: string;
constructor(firstName:string, lastName: string, age: number, ssn: string) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this._ssn = ssn;
}
}
var p = new Person("John", "Smith", 29, "123-90-4567");
console.log("Last name: " + p.lastName + " SSN: " + p._ssn);
Обратите внимание: имя закрытой переменной начинается со знака подчеркивания: _ssn. Тем самым в отношении закрытых свойств соблюдается соглашение об именах.
В последней строке кода листинга Б.6 предпринимается попытка доступа к закрытому свойству _ssn извне, поэтому анализатор кода TypeScript выдаст ошибку компиляции: Property 'ssn' is private and is only accessible in class 'Person'. Но пока компилятор не будет запущен с ключом --noEmitOn-Error, код с ошибкой все равно будет транспилирован в JavaScript:
var Person = (function () {
function Person(firstName, lastName, age, _ssn) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this._ssn = _ssn;
}
return Person;
})();
var p = new Person("John", "Smith", 29, "123-90-4567");
console.log("Last name: " + p.lastName + " SSN: " + p._ssn);
Ключевое слово private придает свойство закрытости только в коде TypeScript. IDE-среды не станут показывать закрытые элементы в контекстно зависимой помощи при попытке доступа к свойствам объекта извне, но в создаваемом коде JavaScript все свойства и методы класса будут все равно рассматриваться как открытые.
TypeScript позволяет предоставлять модификаторы доступа с аргументами конструктора, как показано в следующей короткой версии класса Person (листинг Б.7).
Листинг Б.7. Использование модификаторов доступа
class Person {
constructor(public firstName: string,
public lastName: string, public age: number, private _ssn: string) {
}
}
var p = new Person("John", "Smith", 29, "123-90-4567");
При использовании конструктора с модификаторами доступа компилятор TypeScript воспринимает модификатор в качестве инструкции по созданию и сохранению свойств класса, соответствующих аргументам конструктора. Их не нужно объявлять и инициализировать явным образом. Обе версии класса Person, как короткая, так и длинная, приводят к созданию одного и того же кода JavaScript.
Когда функция объявляется в классе, ее называют методом. В JavaScript методы нужно объявлять в прототипе объекта; но в классе метод объявляется путем указания имени, за которым ставятся круглые и фигурные скобки, как это делается в других объектно-ориентированных языках.
В следующем фрагменте кода показано, как можно объявить и использовать класс MyClass с методом doSomething(), имеющим один аргумент и не имеющим возвращаемого значения (листинг Б.8).
Листинг Б.8. Создание метода
class MyClass{
doSomething(howManyTimes: number): void{
// выполнение какой-либо операции
}
}
var mc = new MyClass();
mc.doSomething(5);
Статические элементы и элементы экземпляра В коде листинга Б.8, а также в коде класса, показанного на рис. Б.4, сначала создается экземпляр класса, а затем происходит обращение к его элементам с помощью ссылочной переменной, указывающей на этот экземпляр: mc.doSomething(5); Если при объявлении свойства или метода применялось ключевое слово static, то его значения будут совместно использоваться всеми экземплярами класса, и вам не придется создавать экземпляр для доступа к статическим элементам. Вместо ссылочной переменной (такой как mc) нужно задействовать имя класса: class MyClass{ static doSomething(howManyTimes: number): void{ // выполнение какой-либо операции } } MyClass.doSometing(5); Если создан экземпляр класса и нужно вызвать метод класса из другого метода, объявленного в том же самом классе, то следует воспользоваться ключевым словом this (например, this.doSomething(5)). В других языках программирования применять это слово в коде класса необязательно, но компилятор TypeScript сообщит, что не может найти метод без явного использования this. |
Добавим к классу Person открытые сеттер и геттер, чтобы появилась возможность устанавливать и получать значение свойства _ssn (листинг Б.9).
Листинг Б.9. Добавление сеттера и геттера
В листинге B.9 в геттере и сеттере не содержится никакой логики приложения, а вот в настоящих приложениях в этих методах будет проводиться проверка на приемлемость. Например, код в геттере и сеттере может проверять авторизацию того, кто их вызывает, без которой получить или установить значение _ssn невозможно.
ПРИМЕЧАНИЕ
Начиная со спецификации ES5, в JavaScript также поддерживаются геттеры и сеттеры.
Обратите внимание: в методах для доступа к свойству объекта используется ключевое слово this. В TypeScript это обязательное условие.
В JavaScript поддерживается прототипное наследование на основе объектов, где один объект может использовать другой в качестве прототипа. Для наследования в классах в TypeScript, как в ES6 и в других объектно-ориентированных языках, имеется ключевое слово extends. Но в ходе транспиляции в JavaScript в создаваемом коде применяется синтаксис прототипного наследования.
На рис. Б.6 представлено создание класса Employee (строка 9), расширяющего класс Person (показано на снимке экрана из интерактивной среды TypeScript). В правой части можно увидеть транспилированную JavaScript-версию, в которой используется прототипное наследование. TypeScript-версия кода гораздо лаконичнее и понятнее.
Добавим к классу Employee конструктор и свойство department (листинг Б.10).
Листинг Б.10. Использование наследования
Рис. Б.6. Наследование классов в TypeScript
Если вызвать метод, объявленный в родительском классе, в отношении объекта, относящегося к подклассу, то можно будет воспользоваться именем этого метода, как будто он был объявлен в подклассе. Но иногда необходимо вызвать конкретно тот метод, который определен в родительском классе, и тогда следует задействовать ключевое слово super.
Ключевое слово super можно применять двумя способами. В конструкторе подкласса оно служит в качестве вызова метода. Кроме того, оно может, в частности, использоваться для вызова метода родительского класса. Обычно оно применяется с переопределением метода. Например, если и родительский класс, и его потомок имеют метод doSomething(), то потомок может задействовать функциональность, запрограммированную в родительском классе, и добавить к имеющимся другие свойства:
doSomething(){
super.doSomething();
// Сюда добавляются дополнительные функциональные свойства
}
Дополнительно о ключевом слове super можно прочитать в подразделе A.7.4. Мы уже дошли до половины данного приложения, поэтому сделаем передышку и просмотрим то, что изучено до сих пор.
• Несмотря на то что Angular-приложения можно создавать, используя синтаксис JavaScript, соответствующий спецификации ES5 или ES6, применение TypeScript имеет преимущества на стадии разработки вашего проекта.
• TypeScript позволяет объявлять типы элементарных переменных, а также разрабатывать собственные типы. Транспиляторы удаляют информацию о типах, поэтому ваше приложение может быть развернуто на любом браузере, поддерживающем синтаксис ECMAScript 3, 5 или 6.
• Компилятор TypeScript превращает .ts-файлы в их .js-аналоги. Его можно запустить в режиме отслеживания, тогда проводимые им преобразования станут инициироваться при любом изменении в любом .ts-файле.
• Классы TypeScript придают коду более декларативный вид. Концепция классов и наследования хорошо знакома разработчикам, использующим другие объектно-ориентированные языки.
• Модификаторы доступа помогают управлять доступом к элементам класса в ходе разработки, но не так строги, как их аналоги в языках, подобных Java и C#.
Начиная со следующего раздела, мы продолжим знакомство с дополнительными синтаксическими конструкциями TypeScript, но если вы хотите увидеть совместную работу TypeScript и Angular, то можете просто перейти к изучению раздела Б.9.
В TypeScript поддерживаются параметризированные типы, известные также как обобщения, которые могут использоваться в различных сценариях. Например, можно создать функцию, способную получать значение любого типа, но в ходе ее вызова из конкретного контекста явно указать конкретный тип.
Возьмем еще один пример: в массиве способны храниться объекты любого типа, но можно указать, какие именно типы объектов (например, экземпляры класса Person) разрешено там хранить. Если вы (или кто-нибудь еще) попытаетесь добавить объект другого типа, то компилятор TypeScript выдаст ошибку.
В следующем фрагменте кода (листинг Б.11) объявляется класс Person, создаются два его экземпляра, которые сохраняются в массиве workers, объявленном с обобщенным типом. Такие типы обозначаются путем помещения их в угловые скобки (например, <Person>).
Листинг Б.11. Использование обобщенного типа
class Person {
name: string;
}
class Employee extends Person{
department: number;
}
class Animal {
breed: string;
}
var workers: Array<Person> = [];
workers[0] = new Person();
workers[1] = new Employee();
workers[2] = new Animal(); // ошибка в ходе компиляции
В этом фрагменте кода объявляются классы Person (Люди), Employee (Работники) и Animal (Животные), а также массив workers (рабочие) с обобщенным типом <Person>. Таким образом анонсируются планы сохранения только экземпляров класса Person или его потомков. При попытке сохранения в том же самом массиве экземпляра класса Animal в ходе компиляции будет выдана ошибка.
При работе в организации, где в качестве работников выступают животные (например, полицейские собаки), можно изменить объявление workers следующим образом:
var workers: Array<any> = [];
ПРИМЕЧАНИЕ
В разделе Б.8 будет показан еще один пример использования обобщений. Там мы объявим массив workers array, имеющий тип интерфейса.
Можно ли применять обобщенные типы с любым объектом или функцией? Нет. Создатель объекта или функции должен выдать на это разрешение. Если открыть файл определения типов TypeScript (lib.d.ts) на GitHub на и провести поиск по строке interface Array, можно будет увидеть объявление Array, показанное на рис. Б.7. (Файлы определения типов рассматриваются в разделе Б.10.)
Рис. Б.7. Фрагмент кода lib.d.ts с описанием API Array
Обозначение <T> в строке 1008 свидетельствует, что TypeScript позволяет объявлять параметр типа с Array, и компилятор будет проверять предоставление в вашей программе конкретного типа. В листинге Б.11 этот обобщенный параметр <T> указывается как <Person>. Но поскольку обобщения в ES6 не поддерживаются, в созданном транспилятором коде вы их не увидите. Это всего лишь дополнительные меры безопасности для разработчиков на этапе компиляции.
В строке 1022 на рис. Б.7 можно увидеть еще один символ T. Когда обобщенные типы указываются с аргументами функций, угловые скобки не нужны. Но реальный тип T в TypeScript отсутствует. Здесь T означает, что метод push позволяет внедрять в массив объекты определенного типа, как в следующем примере:
workers.push(new Person());
В данном разделе проиллюстрирован всего один вариант работы с обобщенными типами в массиве, который уже поддерживает обобщения. Можно также создавать собственные классы или функции, поддерживающие обобщения. Если где-либо в коде будет предпринята попытка вызова функции saySomething() с предоставлением неверного типа аргумента, то компилятор TypeScript выдаст ошибку:
Созданный код JavaScript не включает никакую информацию, касающуюся обобщения, и предыдущий фрагмент кода будет транспилирован в следующий код:
function saySomething(data) {
}
saySomething("Hello");
saySomething(123);
Получить более подробную информацию об обобщениях можно в разделе Generics в справочнике по TypeScript ().
Концепция интерфейсов, которая в других объектно-ориентированных языках используется для введения кодового контракта, обязательного к соблюдению со стороны API, в JavaScript не поддерживается. Примером контракта может послужить то, что в классе X объявляется реализация интерфейса Y. Если класс X не будет включать реализацию методов, объявленных в интерфейсе Y, то это будет считаться нарушением контракта и код не пройдет компиляцию.
В TypeScript для поддержки интерфейсов включены ключевые слова interface и implements, но интерфейсы в код JavaScript не транспилируются. Они просто помогают избегать использования неверных типов в ходе разработки.
Для применения интерфейсов в TypeScript имеются две схемы.
• Объявление интерфейса, определяющего пользовательский тип, содержащий несколько свойств. Затем объявление метода, имеющего аргумент такого типа. При вызове этого метода компилятор проверит, включены ли в объект, переданный в качестве аргумента, все свойства, объявленные в интерфейсе.
• Объявление интерфейса, включающего абстрактные (нереализованные) методы. Когда класс объявляет, что реализует (implements) этот интерфейс, данный класс должен предоставить реализацию всех абстрактных методов.
Рассмотрим реализацию этих двух схем на примерах.
При использовании JavaScript-сред вам могут попадаться API, требующие в качестве параметра функции некий конфигурационный объект. Чтобы выяснить, какие свойства должны быть предоставлены в этом объекте, нужно либо открыть документацию по API, либо прочитать исходный код среды. В TypeScript можно объявить интерфейс, включающий все свойства и их типы, которые должны присутствовать в объекте конфигурации.
Посмотрим, как это можно сделать в классе Person, содержащем конструктор с четырьмя аргументами: firstName, lastName, age и ssn. На сей раз будет объявлен интерфейс IPerson, содержащий четыре элемента, а конструктор класса Person будет изменен для использования в качестве аргумента объекта этого пользовательского типа (листинг Б.12).
Листинг Б.12. Объявление интерфейса
В TypeScript имеется структурная система типов, что означает совместимость двух различных типов в том случае, если оба они включают одни и те же элементы. Наличие одинаковых элементов говорит о наличии у них одинаковых имен и типов. В коде листинга Б.12, даже если не указывать тип переменной aPerson, она все равно будет считаться совместимой с IPerson и может использоваться при создании экземпляра объекта Person в качестве аргумента конструктора.
При изменении имени или типа одного из элементов IPerson компилятор TypeScript выдаст ошибку. С другой стороны, при попытке создать экземпляр Person, содержащий объект со всеми требуемыми элементами IPerson и некоторые другие элементы, красный флажок поднят не будет. В качестве аргумента конструктора Person можно использовать следующий объект:
var anEmployee: IPerson = {
firstName: "John",
lastName: "Smith",
age: 29,
department: "HR"
}
Элемент department не был определен в интерфейсе IPerson, но, поскольку у объекта имеются все остальные элементы, перечисленные в интерфейсе, условия контракта соблюдены.
В интерфейсе IPerson не определяются никакие методы, но в интерфейсы TypeScript могут включаться сигнатуры методов без реализации.
Ключевое слово implements может применяться с объявлением класса, чтобы аннотировать факт реализации классом конкретного интерфейса. Предположим, имеется интерфейс IPayable, который объявлялся следующим образом:
interface IPayable{
increase_cap:number;
increasePay(percent: number): boolean
}
Теперь класс Employee может объявить, что реализует интерфейс IPayable:
class Employee implements IPayable{
// Сюда помещается код реализации
}
Прежде чем перейти к подробностям реализации, ответим на следующий вопрос: «Почему бы просто не написать весь требуемый код в классе, не выделяя часть кода в интерфейс?» Предположим, нужно создать приложение, позволяющее поднимать заработную плату работникам вашей организации. Можно создать класс Employee (расширяющий класс Person) и включить в него метод increaseSalary(). Тогда бизнес-аналитики могут попросить вас добавить возможность повысить выплаты подрядчикам, работающим на вашу фирму. Но подрядчики представлены именами своих компаний и идентификационными номерами, на них не распространяется понятие заработной платы, и расчеты с ними ведутся на почасовой основе.
Вы можете создать еще один класс для подрядчиков по имени Contractor (который не является наследником класса Person), включающий некие свойства и метод для поднятия почасовой оплаты increaseHourlyRate(). Теперь у вас имеются два различных API: один для поднятия заработной платы работникам, а другой для поднятия почасовой выплаты подрядчикам. Более рациональным решением станет создание общего интерфейса IPayable и классов Employee и Contractor, предоставляющих, как показано далее, разные реализации IPayable для этих классов (листинг Б.13).
Листинг Б.13. Использование нескольких реализаций интерфейса
Запуск кода листинга Б.13 выведет на консоль браузера следующую информацию:
Increasing salary by 30
Sorry, the increase cap for contractors is 20
Зачем объявлять классы с ключевым словом implements В листинге Б.13 показано структурное подтипирование TypeScript. Если убрать из объявления либо Employee, либо Contractor фрагмент кода implements Payable, то код сохранит работоспособность и компилятор не станет возражать насчет строк кода, добавляющих эти объекты к массиву workers. Компилятор обладает достаточной логикой, чтобы понять, что, даже если в классе реализация IPayable (implements IPayable) явным образом не объявлена, метод increasePay() в нем все равно должным образом реализуется. Но если убрать фрагмент кода implements IPayable и попытаться изменить сигнатуру метода increasePay() в любом из классов, то объект такого класса поместить в массив workers не удастся, потому что этот объект уже не будет относиться к типу IPayable. Кроме того, без ключевого слова implements будет нарушена поддержка IDE-среды (например, проведение с ее помощью реструктуризации кода). |
В TypeScript имеется интересное свойство, известное как вызываемый, или callable-интерфейс, который содержит пустую сигнатуру функции (сигнатуру без имени функции). В следующем примере кода показана пустая сигнатура функции, получающей один параметр типа number и возвращающей значение типа boolean:
(percent: number): boolean;
Пустая сигнатура показывает, что интерфейс относится к разряду вызываемых (callable). В листинге Б.14 будет показана другая версия объявления IPayable, содержащая пустую сигнатуру функции. Для краткости наследование удалено из данного примера. В нем будут объявлены отдельные функции, реализующие правила для повышения выплат работникам и подрядчикам. Эти функции будут передаваться в качестве аргументов и вызываться конструктором класса Person.
Листинг Б.14. Callable-интерфейс с пустой функцией
Запуск кода листинга Б.14 выведет на консоль браузера следующую информацию:
Increasing salary by 30
Sorry, the increase cap for contractors is 20
Интерфейсы поддерживают наследование с помощью ключевого слова extends. Если в классе реализуется интерфейс A, являющийся расширением интерфейса B, то в классе должны быть реализованы все элементы из A и B.
Трактовка классов как интерфейсов В TypeScript о любом классе можно думать, как об интерфейсе. Если имеются объявления class A {} и class B {}, то вполне допустимо будет написать class A implements B {}. Пример такого синтаксиса можно увидеть в разделе 4.4. При транспиляции в JavaScript интерфейсы TypeScript не создают какой-либо вывод, и если поместить в отдельный файл только объявление интерфейса (например, в файл ipayable.ts) и скомпилировать его с помощью tsc, то будет создан пустой файл ipayable.js. При загрузке кода, импортирующего интерфейс из файла (например, из ipayable.js), используя SystemJS, будет получена ошибка, поскольку импортировать пустой файл нельзя. Нужно сообщить SystemJS, что IPayable следует рассматривать в качестве модуля, и зарегистрировать его в глобальном реестре System. Этот можно сделать в ходе конфигурирования SystemJS с помощью показанной ниже аннотации meta: System.config({ transpiler: 'typescript', typescriptOptions: {emitDecoratorMetadata: true}, packages: {app: {defaultExtension: 'ts'}}, meta: { 'app/ipayable.ts': { format: 'es6' } } |
Кроме предоставления способа создания пользовательских типов и минимизации количества ошибок, связанных с несоответствиями типов, механизм интерфейсов существенно упрощает реализацию шаблона проектирования «Внедрение зависимостей», рассмотренного в главе 4.
На этом краткое введение в интерфейсы завершается. Более подробные сведения о них можно найти в разделе Interfaces публикации TypeScript Handbook ().
ПРИМЕЧАНИЕ
Удобным средством создания документации программы на основе комментариев вашего кода TypeScript является утилита TypeDoc. Ее можно получить на сайте .
Мы практически завершили наш обзор синтаксиса TypeScript. Пора объединить его с Angular.
Существует несколько определений понятия метаданных. Популярным определением является такое: метаданные — это данные о данных. Мы рассматриваем метаданные в качестве данных, которые дают описание кода. Декораторы TypeScript предоставляют способ добавления метаданных к вашему коду. В частности, чтобы превратить класс TypeScript в компонент Angular, его можно проаннотировать с помощью метаданных. Аннотации начинаются со значка @.
Чтобы превратить класс TypeScript в UI-компонент Angular, необходимо отдекорировать его, задействуя аннотацию @Component. Angular воспользуется внутренним механизмом для синтаксического разбора ваших аннотаций и создаст код, добавляющий запрошенное поведение к классу TypeScript:
@Component({
// Сюда нужно включить селектор (имя) для идентификации компонента
// в HTML-документе.
// Предоставьте свойство шаблона фрагменту HTML, чтобы отобразить компонент.
// Сюда также помещается стилевое оформление компонента.
})
class HelloWorldComponent {
// Сюда помещается код, реализующий имеющуюся в компоненте
// логику приложения.
}
При использовании аннотаций необходимо наличие обработчика аннотаций, способный провести синтаксический разбор содержимого аннотации и превратить его в код, понятный механизму выполнения кода (движку JavaScript браузера). Обязанности обработчика аннотаций в контексте данной книги выполняет Angular-компилятор ngc.
Для применения аннотаций, поддерживаемых Angular, нужно импортировать их реализацию в код вашего приложения. Например, следует импортировать аннотацию @Component из модуля Angular:
import { Component } from 'angular2/core';
Хотя реализация данных аннотаций выполнена на Angular, может появиться потребность в стандартизированном механизме для создания своих собственных аннотаций. Именно для этого и предназначены декораторы TypeScript. Их нужно воспринимать следующим образом: Angular предлагает свои аннотации, позволяющие декорировать ваш код, а TypeScript дает вам возможность создавать свои собственные аннотации с поддержкой декораторов.
На протяжении нескольких лет большое хранилище файлов определения TypeScript под названием DefinitelyTyped было единственным источником определения типов TypeScript для нового ECMAScript API и для сотен популярных сред и библиотек, написанных на JavaScript. Данные файлы предназначались для того, чтобы дать компилятору TypeScript сведения о типах, ожидаемых API этих библиотек. Хотя репозиторий по-прежнему существует, новый репозиторий файлов определения типов расположился на /, и именно он использовался во всех примерах кода, приводившихся в данной книге.
Суффиксом для любого имени файла определения служит d.ts, и файлы определений можно найти в модулях Angular в подкаталогах папки node_modules/@angular после запуска команды npm install в соответствии с инструкциями, приведенными в главе 2. Все требуемые файлы вида *.d.ts связаны с пакетами Angular npm, и в их отдельной установке нет никакой надобности. Присутствие файлов определений в вашем проекте позволит компилятору TypeScript гарантировать, что ваш код использует при вызове Angular API правильные типы.
Например, Angular-приложения запускаются путем вызова метода bootstrapModule() с передачей ему в качестве аргумента корневого модуля вашего приложения. Файл application_ref.d.ts включает следующие определения для этой функции:
bootstrapModule<M>(moduleType: ConcreteType<M>,
compilerOptions?: CompilerOptions | CompilerOptions[]):
Promise<NgModuleRef<M>>;
Прочитав эти определения, вы (и компилятор tsc) узнаете, что данная функция может быть вызвана с одним обязательным параметром типа ConcreteType и с необязательным массивом ключей компилятора. Если файл application_ref.d.ts не был частью вашего проекта, то компилятор TypeScript позволит вызвать функцию bootstrapModule с неправильными параметрами типа или вообще без каких-либо параметров, что приведет к возникновению ошибки в ходе выполнения программы. Но файл application_ref.d.ts имеется, поэтому TypeScript выдаст ошибку в ходе компиляции с сообщением Supplied parameters do not match any signature of call target. Файлы определения типов также позволяют IDE-средам показывать контекстно зависимую помощь при написании кода, вызывающего функции Angular или присваивающего значения свойствам объектов.
Чтобы установить файлы определения типов TypeScript для библиотеки или среды, написанной на JavaScript, разработчики использовали диспетчеры определения типов — tsd и Typings. Первое средство уже устарело, поскольку всего лишь позволяет получать файлы вида *.d.ts из definitelytyped.org. До выхода TypeScript 2.0 мы пользовались средством Typings (), которое позволяло привносить определения типов из произвольно взятого хранилища.
С выходом TypeScript 2.0 необходимость применять диспетчеры определения типов для проектов на основе использования npm отпала. Теперь npm-репозиторий / включает структуру @types, хранящую определения типов для популярных библиотек JavaScript. Здесь упоминаются все библиотеки, фигурирующие на definitelytyped.org.
Допустим, нужно установить файл определения типов для jQuery. Запуск следующей команды приведет к установке определений типов в каталог node_modules/@types и к сохранению этой зависимости в файле package.json вашего проекта:
npm install @types/jquery --save-dev
В данной книге при установке определений типов во многих учебных проектах использовались аналогичные команды. Например, в ES6 для массивов был введен метод, но если ваш TypeScript-проект настроен на выдачу в результате компиляции кода, отвечающего спецификации ES5, то ваша IDE-среда выделит метод find() красным цветом, поскольку в ES5 он не поддерживается. Установка файла определения типов для ES6-shim устранит красноту в вашей IDE-среде:
npm i @types/es6-shim --save-dev
А что будет, если tsc не сможет найти файл определения типов? В период написания книги (TypeScript 2.0) была вероятность, что tsc не найдет файлы определения типов, находящиеся в каталоге node_modules/@types. Если вы столкнетесь с этой проблемой, то добавьте требуемые файлы к разделу types файла tsconfig.json. Вот пример: "compilerOptions": { ... "types":["es6-shim", "jasmine"], } |
Разрешение модулей и тег reference Если не используются модули CommonJS, то добавьте к вашему коду TypeScript явное указание ссылки на требуемые определения типов: /// <reference types="typings/jquery.d.ts" /> Модули CommonJS применяются как ключ tsc, и каждый проект включает в файле tsconfig.json следующий ключ: "module": "commonjs" Когда tsc видит инструкцию import, ссылающуюся на модуль, он автоматически пытается найти файл <имя-модуля>d.ts в каталоге node_modules. Если такой файл не будет найден, то он поднимается на один уровень выше и повторяет процесс. Дополнительные сведения об этой процедуре можно найти в разделе Typings for npm Modules в публикации TypeScript Handbook (). В будущих выпусках tsc аналогичная стратегия будет реализована для разрешения модулей AMD. |
Angular включает все требуемые файлы определений, и вам не нужно применять диспетчер определения типов, если только ваше приложение не использует другие сторонние библиотеки JavaScript. В таком случае для получения контекстно зависимой помощи в вашей IDE-среде их файлы определений придется установить вручную.
В своих d.ts-файлах Angular задействует синтаксис ES6, и для большинства модулей можно воспользоваться следующим синтаксисом импорта: import {Component} from 'angular2/core';. Определения класса Component будут найдены. Вы будете импортировать все остальные модули и компоненты Angular.
TSLint — средство, которым можно воспользоваться в целях обеспечить написание программы в соответствии с указанными правилами и стилями программирования. TSLint можно настроить на проверку того, что код TypeScript в вашем проекте имеет нужные выравнивания и отступы; имена всех интерфейсов начинаются с заглавной буквы I; в именах классов применяется верблюжий регистр (CamelCase) и т.д.
Средство TSLint можно установить глобально с помощью следующей команды:
npm install tslint -g
Чтобы установить узловой модуль TSLint в каталог вашего проекта, запустите такую команду:
npm install tslint
Правила, которые нужно применить к вашему коду, указываются в файле конфигурации tslint.json. Пример файла правил поставляется вместе с TSLint. Этот файл называется sample.tslint.json и находится в каталоге docs. При необходимости конкретные правила можно включать и выключать.
Подробности использования TSLint приведены на сайте . Ваша IDE-среда может поддерживать проверку соблюдения правил с помощью TSLint в исходном состоянии.
IDE-среды Мы хотели придать содержимому книги независимость от той или иной IDE-среды и не включали сюда инструкции, характерные для конкретных сред. Но есть несколько IDE, поддерживающих TypeScript. Наиболее популярными из них являются WebStorm, Visual Studio Code, Sublime Text и Atom. Все эти IDE-среды и редакторы работают под управлением Windows, Mac OS и Linux. Если разработка ваших TypeScript/Angular-приложений ведется на компьютере под управлением Windows, то можно воспользоваться средой Visual Studio 2015. |
Процесс разработки и развертывания TypeScript/Angular-приложений состоит из нескольких этапов, их необходимо по возможности максимально автоматизировать. Для достижения данной цели существует несколько способов. Ниже представлен примерный список этапов, которые могут выполняться для создания Angular-приложения.
1. Создание каталога для вашего проекта.
2. Создание файла package.json с перечислением всех зависимостей вашего приложения, например пакетов Angular, среды тестирования Jasmine и т.д.
3. Установка всех пакетов и библиотек, перечисленных в package.json, с использованием команды npm install.
4. Написание кода приложения.
5. Загрузка вашего приложения в браузер с помощью загрузчика SystemJS, который не только загружает, но и транспилирует TypeScript в JavaScript в браузере.
6. Минимизация и объединение вашего кода и ресурсов в пакеты с использованием Webpack и его дополнительных модулей.
7. Копирование файлов в каталог распространения с использованием сценариев npm.
В главе 2 объясняется, как приступить к новому Angular-проекту и работать с диспетчером пакетов npm и загрузчиком модулей SystemJS.
ПРИМЕЧАНИЕ
Angular CLI — утилита командной строки, которая может создать исходную структуру вашего проекта, сгенерировать компоненты и сервисы и подготовить сборки. Средство Angular CLI описано в главе 10.
ПРИМЕЧАНИЕ
В этом приложении не был упомянут вопрос обработки ошибок, но, поскольку TypeScript является расширением JavaScript, обработка ошибок выполняется точно так же, как и в JavaScript. Прочитать о различных типах ошибок можно в статье JavaScript Reference в разделе Error на ресурсе Mozilla Developer Network ().