Это третья статья из серии, посвященной TypeScript. В первой части было рассказано о системе базовых типов в TypeScript и рассмотрены примитивные типы данных. Во второй статье речь шла о настраиваемых типах, таких как классы и интерфейсы, а также о функциях TypeScript. В третьей части руководства по TypeScript мы остановимся на том, как сделать код TypeScript повторно используемым с помощью таких концепций, как универсальные шаблоны и пространства имен.
Универсальные шаблоны
На практике приложение, построенное с помощью TypeScript, связывается с каким-либо API-интерфейсом для выполнения операций CRUD (создание, чтение, обновление и удаление). Предположим, вы создаете приложение Angular, которое отображает информацию о некоторых продуктах и заказах, оформленных пользователем. В этом случае обычно создается класс службы, который будет выполнять операции CRUD для определенного компонента. Например, вы создаете классы, именуемые ProductsService и OrderService, для управления продуктами и заказами соответственно.
Жизнь без универсальных шаблонов
Рассмотрим класс ProductsService, который использует Angular HTTPClient, чтобы направлять вызовы в API?интерфейс (листинг 1).
Теперь предположим, что приложению требуется вывести информацию о заказе пользователям. В этом случае следует создать класс OrdersService для управления заказами. Но по мере развития приложения вы создаете много таких классов службы, которые выполняют операции CRUD для каждого компонента. Это проблема, так как код приходится многократно повторять; в итоге возникает ситуация, когда база кода становится очень негибкой.
Универсальные классы
Повторение кода противоречит оптимальным подходам к разработке (принцип DRY, или don’t repeat yourself — «не повторяйся»). В TypeScript универсальные шаблоны помогают пользователям соблюдать принцип DRY. Рассмотрим, как, используя HTTPClient для выполнения операций CRUD, можно сократить приведенный в листинге 1 код с нескольких классов службы до единственного класса и каким образом соответствующие классы службы (ProductsService и OrdersService) могут задействовать этот универсальный класс. Простой пример приведен в листинге 2.
Класс ProductsService может просто использовать универсальный класс API, тогда вам не придется беспокоиться относительно кода HTTPClient. Любое число создаваемых вами классов службы может использовать класс API.
Универсальные методы
Аналогично универсальным классам, в TypeScript можно создавать универсальные функции. Упомянутый выше класс API может быть заменен на простой, не универсальный класс с типовыми функциями. Взгляните на программный код, приведенный в листинге 3.
При использовании типовой функции TypeScript автоматически выводит тип возвращаемого значения, если не указан аргумент универсального типа. То же демонстрируется в листинге 4.
Изменение типа возвращаемого значения с Products на Discounts для функции getById() автоматически подразумевает объект Discount в качестве типа возвращаемого значения (листинг 5).
Универсальные ограничения
В некоторых случаях полезно добавить ограничения, чтобы разрешить передачу определенного типа в качестве универсального аргумента. Например, в указанном выше классе API можно передать любой тип (примитивные типы данных, в том числе числа, строки, логические значения, или настраиваемый тип, такой как класс Products или интерфейс). Предположим, что универсальный класс или типовая функция должны принимать только типы, соответствующие условию. В таких случаях необходимо добавить универсальное ограничение. В листинге 6 показано, как это сделать.
Сначала создайте контракт, то есть, проще говоря, создайте интерфейс.
interface IGenericConstraint { hasPassedValidation: boolean; }
Аргумент типовых функций класса API должен соответствовать контракту. Для этого программный код должен выглядеть следующим образом:
export class Api { public getMany(): Array { // make an HTTP GET request to your API. } }
Класс ProductsService должен использовать класс Products для реализации этого интерфейса; в противном случае возникнет ошибка компиляции (листинг 6).
После того как интерфейс реализован в IgenericConstraints в классе Products, можно начать передавать Products как аргумент универсального типа.
Пространства имен
Последняя тема нашего руководства по TypeScript — пространства имен.
По мере развития приложения разработчикам важно организовать управление программным кодом. В JavaScript для этого используются модули; в TypeScript — пространства имен. Пространство имен гарантирует, что компоненты изолированы. Чтобы разрешить использование этих компонентов внешними функциями, необходимо экспортировать пространства имен, воспользовавшись ключевым словом export. Ниже приводится простой пример применения пространств имен в TypeScript.
export namespace ApiClient { export class Api { } }
Обратите внимание, что ключевое слово export добавлено как для пространства имен, так и для класса. Ключевое слово export позволяет задействовать пространство имен ApiClient и его экспортированное содержимое в других файлах. В листинге 7 приведен пример использования класса Api в классе Orders.
Первый Api — имя файла. ApiClient — пространство имен, а внутри этого пространства имен находится класс Api. Вы импортируете этот класс из файла Api. ts и назначите его переменной, которая в описанном выше случае назначена Api1. На приведенном экране можно увидеть пример структуры тестового приложения.
Экран. Пример структуры тестового приложения |
export class ProductsService { private readonly httpClient: HttpClient; constructor(httpClient: HttpClient) { this.httpClient = httpClient; } public getProducts(): Array { return new Array(); } public getProductById(id: number): Products { return new Products(); } public postProduct(product: Products): void { // make an HTTP POST request to your API. } public deleteProduct(id: number): void { // make an HTTP DELETE request to your API. } }
export class Api { private readonly httpClient: HttpClient; constructor(httpClient: HttpClient) { this.httpClient = httpClient; } public getMany(): Array { // make an HTTP GET request to your API. } public getById(id: number): T { // make an HTTP GET request to your API. } public post(product: Products): void { // make an HTTP POST request to your API. } public delete(id: number): void { // make an HTTP DELETE request to your API. } }
export class Api { private readonly httpClient: HttpClient; constructor(httpClient: HttpClient) { this.httpClient = httpClient; } public getMany(): Array { // make an HTTP GET request to your API. } public getById(id: number): T { // make an HTTP GET request to your API. } public post(object: T): void { // make an HTTP POST request to your API. } public delete(id: number): void { // make an HTTP DELETE request to your API. } }
import Api = require("./Api"); export namespace OrderService { import Api1 = Api.ApiClient.Api; export class Orders { private readonly api: Api1; } }