В первой статье серии по TypeScript были изложены сведения об основных типах данных, таких как числа, строки, массивы и т. д. Во второй части речь пойдет о том, как создаются функции, каково назначение классов, почему важны интерфейсы и как работают стрелочные функции, появившиеся в ECMAScript 6.

Функции

Функции — универсальные строительные блоки внутри класса, содержащие некоторую бизнес-логику. Процесс создания функции в TypeScript похож на существующий в JavaScript: используется ключевое слово function. Однако в TypeScript есть возможность определить тип параметров и тип возвращаемого функцией значения. В листинге 1 показана простейшая реализация функции в TypeScript. В приведенном примере была создана функция с именем getProductCode, принимающая единственный параметр строкового типа. Текст: string перед символом фигурной скобки указывает, что эта функция возвращает строку. Если вы не хотите возвращать никаких значений, можно просто указать void вместо string.

Эта функция гораздо удобнее для прочтения и понимания: она принимает параметры типа string и возвращает строку.

Использование необязательных параметров и параметров по умолчанию

Параметр по умолчанию — это параметр, которому присваивается значение по умолчанию, а необязательному параметру не требуется передавать значение. Оба предоставляют сходные преимущества, но с небольшими различиями. Взгляните на программный код, приведенный в листинге 2. Мы возвращаем undefined только в качестве примера.

В приведенном в листинге 2 программном коде видно, что productType присвоено значение 1, а к productCode добавлен символ вопросительного знака. Здесь productType — параметр по умолчанию, а productCode — необязательный параметр. Если нужно вызвать данную функцию, TypeScript позволяет сделать это без значения параметра, присвоенного productCode. Поскольку значение для параметра productType не передано при вызове функции, TypeScript передает значение по умолчанию 1 в соответствии с логикой обработки.

Параметры REST

В ECMAScript 6 появились параметры REST. Они позволяют передать несколько аргументов функции. Однако необходимо выполнить несколько условий: чтобы создать параметр REST, добавьте три точки перед именем параметра. Параметр REST должен всегда быть последним в списке параметров и всегда должен быть массивом нужного вам типа. Параметры REST необязательные, то есть программный код будет работать, даже если им не переданы никакие значения. В листинге 3 приводится простой пример реализации параметра REST в функции.

Стрелочные функции

Если вы знакомы с языком C#, то, вероятно, слышали о лямбда-выражениях. В TypeScript похожий синтаксис используется для стрелочных функций. Благодаря стрелочным функциям вводить слово function при создании функции не требуется.

В листинге 4 приводится пример создания и использования стрелочных функций, обозначаемых лямбда-символом (=>).

Классы

Классы представляют собой схемы, определяющие форму и структуру объекта. Класс в TypeScript может содержать любое число свойств, функций, методов получения и задания; создать их чрезвычайно просто. С помощью классов можно выполнять инкапсуляцию, назначая различные модификаторы доступа (открытый, закрытый и защищенный в приведенном в листинге 5 программном коде). В листинге 5 показан пример класса с некоторыми свойствами (с различными модификаторами доступа), функциями и конструктором.

С помощью модификаторов доступа можно ограничить доступ к определенным свойствам и функциям. Открытые элементы доступны и вне этого класса. Закрытые элементы вне класса недоступны. Функционирование защищенных элементов — такое же, как у закрытых, с одним исключением: любой класс, производный от данного класса, может обращаться к защищенным элементам.

Методы доступа

Методы доступа в классе представляют собой методы получения и задания (демонстрируется в листинге 6 инструкциями get и set), с помощью которых можно манипулировать внутренними элементами в объекте. В листинге 6 показано, как создать и использовать методы получения и задания в классе.

В приведенных примерах демонстрируется, как создать классы и определить конструкторы, свойства и функции. Эти элементы называются экземплярными, потому что они доступны лишь в случае, если создан экземпляр класса. На приведенном в листинге 6 obj — экземпляр класса Products; этот экземпляр используется для доступа к внутренним элементам.

Статические элементы

TypeScript поддерживает и статические элементы; к любому элементу, объявленному как статический, можно обращаться напрямую с использованием самого класса. Создавать экземпляр (как obj в приведенном в листинге 6 примере) для доступа к статическим элементам необходимости нет. Рассмотрим небольшой пример, приведенный в листинге 7.

Если взглянуть на транскомпилированный программный код из примера в листинге 7, то можно заметить, что общедоступная функция getProductType доступна через пример прототипа, тогда как статический метод доступен напрямую на классе Products (листинг 8).

Есть ли различия в применении статических и экземплярных элементов? Ответ неоднозначен. С точки зрения производительности статический элемент немного предпочтительнее, поскольку он не зависит от цепочки прототипа; однако выигрыш в производительности ничтожен.

Выбор между статическими и экземплярными элементами зависит от поставленных задач. Если функция или иной элемент не требует зависимости от экземплярного элемента, то лучше сделать его статическим на уровне класса.

Абстрактные классы

Абстрактные классы похожи на любой другой обычный класс в TypeScript. Их элементы могут иметь реализацию или не иметь ее. Чтобы отметить класс или его элементы как абстрактные, используйте ключевое слово abstract. Главное различие между абстрактными и неабстрактными классами в том, что TypeScript не позволяет создать экземпляр абстрактного класса, поэтому обычно они используются как базовые классы для других классов. На приведенном в листинге 9 примере показано, что, если попытаться создать экземпляр абстрактного класса Discount, TypeScript сообщает об ошибке.

Абстрактный класс может содержать как абстрактные, так и неабстрактные элементы. Абстрактные элементы должны быть определены без деталей реализации, а неабстрактные элементы могут иметь их (листинг 10).

Интерфейсы

Концепция интерфейсов зародилась в Java и C#. Интерфейсы похожи на контракты, определяющие, что находится внутри объекта. Любой класс, который реализует интерфейс, должен соответствовать контракту и предоставлять реализацию. TypeScript немедленно выдает сообщение об ошибке, если класс не соответствует контракту.

Создать интерфейс в TypeScript чрезвычайно просто. Типичный пример интерфейса TypeScript показан в листинге 11.

В приведенном примере созданы интерфейс с некоторыми свойствами и функция с именем generateCode(), которая принимает число как параметр и возвращает число. Любой класс, который реализует этот интерфейс, должен обеспечить реализацию для всех элементов интерфейса. В листинге 12 показан пример класса ElectronicProducts, который реализует интерфейс Iproducts, но не включает свойство productType.

В редакторе кода (лис­тинг 13) Type­Script немедленно выдает ошибку, так как свойство productType не реализовано классом. Этот пример показывает, что необходимо всегда реализовывать все элементы интерфейса; в противном случае возникает ошибка во время компиляции.

Концепция интерфейсов — внутренняя для TypeScript. Когда приведенный в листинге 13 код транскомпилируется в JavaScript, весь код интерфейса и связанный с классами удаляется и остается код прототипа JavaScript. В листинге 14 показан сформированный файл JavaScript.

Элементы только для чтения и необязательные элементы интерфейсов

Продолжим рассмотрение приведенного выше примера. Есть ситуации, когда код скидки не обязателен для всех типов продуктов и нет необходимости изменять код продукта извне. В таких случаях следует отметить свойство кода как readonly (только для чтения) и сделать функцию generateDiscountCode необязательной, как в приведенном в листинге 15 примере.

Обратите внимание на ключевое слово readonly, добавленное перед свойством code. Чтобы сделать функцию необязательной, к концу имени функции добавлен вопросительный знак. Если какой-нибудь класс пытается реализовать этот интерфейс, не обязательно реализовывать функцию generateDiscountCode. Аналогично присвоение значения свойству code приводит к ошибке. В листинге 16 показаны два примера классов, реализующих этот интерфейс.

Во втором из приведенных в листинге 16 примеров класс Furnitures не имеет реализации для функции generateDiscountCode(), но код TypeScript по-прежнему будет работать. Аналогично, если попытаться создать экземпляр любого из этих двух классов и присвоить какое-то значение свойству code, как в примере в листинге 17, TypeScript немедленно выдаст ошибку.

Расширение интерфейсов

В этом разделе мы продолжим использовать интерфейс IProducts, но в демонстрационных целях удалим функцию generateDiscountCode(). Вместо нее будут созданы еще два интерфейса, обеспечивающие скидку для определенной группы пользователей. Программный код выглядит так, как показано в листинге 18.

Обратите внимание, как интерфейс IProducts расширяет интерфейсы ISpecialDiscounts и IbasicDiscounts с использованием ключевого слова extends. Любой класс, который реализует интерфейс IProducts, должен иметь реализацию для функций в интерфейсах, связанных со скидками (потому что они не отмечены как необязательные). Реализация этого интерфейса выглядит так, как показано в листинге 19.

В следующей части руководства по TypeScript речь пойдет об универсальных шаблонах и пространствах имен.

Листинг 1. Функции в TypeScript 
function getProductCode(productId: string): string {
    return `CODE-${productId}`;
}
Листинг 2. Функция showProducts
function showProducts(productType: number = 1, productCode?: string): Array {
    // Implement your business logic that uses the productType parameter and returns an array
    of IProducts.
    return undefined;
}
Листинг 3. Пример реализации параметра REST в функции
function doSomethingWithProducts(productType: number, ...productNames: string[]) {
    // Implement your business logic
}

    this.doSomethingWithProducts(5, ["Electronics", "Furnitures", "Crockeries"]);
    this.doSomethingWithProducts(5);
Листинг 4. Пример создания и использования стрелочных функций
let getNewCode = (userCode: string, productCode: string): string => {
    return `${userCode}-${productCode}`;
}
    this.getNewCode("123", "456");
Листинг 5. Пример класса со свойствами, функциями и конструктором
class Products {
    public readonly id: number;
    public name: string;
    private productCode: string;
    protected price: number;
    constructor(code: string) {
    this.productCode = code;
    }
    public getProductDetails(code: string): any {
        return "";
    }

    private applyDiscount(code: string): void {
        // business logic
    }
}
Листинг 6. Создание и использование методов получения и задания в классе

Листинг 6. Создание и использование методов получения и задания в классе

 

 

 

 

 

 

 

 

 

 

 

 

 

Листинг 7. Доступ к статическим элементам
Листинг 7. Доступ к статическим элементам
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Листинг 8. Статический метод доступен напрямую в классе Products
Листинг 8. Статический метод доступен напрямую в классе Products
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Листинг 9. Ошибка при создании экземпляра абстрактного класса
Листинг 9. Ошибка при создании экземпляра абстрактного класса
 

 

 

 

 

 

Листинг 10. Пример абстрактного класса
Листинг 10. Пример абстрактного класса
 

 

 

 

 

 

 

 

Листинг 11. Пример интерфейса
interface IProducts {
    id: number;
    name: string;
    code: string;
    price: number;
    productType: number;

    generateDiscountCode(code: number): string;
}
Листинг 12. Класс ElectronicProducts реализует интерфейс Iproducts
class Electronics implements IProducts {
    id: number;
    name: string;
    code: string;
    price: number;
    productType: number;

    generateDiscountCode(code: number): string {
        let discountCode = `CODE${code}`;
        return discountCode;
    }
}
Листинг 13. Ошибка, так как свойство productType не реализовано классом
Листинг 13. Ошибка, так как свойство productType не реализовано классом
 

 

 

 

 

 

 

 

 

Листинг 14. Сформированный файл JavaScript
Листинг 14. Сформированный файл JavaScript
 

 

 

 

 

 

 

 

Листинг 15. Свойство кода readonly и необязательная функция generateDiscountCode
interface IProducts {
    id: number;
    name: string;
    readonly code: string;  //Readonly property
    price: number;
    productType: number;

    generateDiscountCode?(code: number): string;    // Optional function
}
Листинг 16. Два примера классов, реализующих интерфейс
class Electronics implements IProducts {
    id: number;
    name: string;
    code: string;
    price: number;
    productType: number;
    constructor() {
    this.code = "Some unique code for every product";
    }

    generateDiscountCode(code: number): string {
        let discountCode = `CODE${code}`;
        return discountCode;
    }
}

class Furnitures implements IProducts {
    id: number;
    name: string;
    code: string;
    price: number;
    productType: number;

    constructor() {
    this.code = "Some unique code for every product";
    }
}
Листинг 17. Попытка присвоить значение свойству code выдает ошибку
Листинг 17. Попытка присвоить значение свойству code выдает ошибку
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Листинг 18. Интерфейс Iproducts без функции generateDiscountCode().
interface ISpecialDiscounts {
    generateDiscountCodeForVIPUsers(userCode: string): string;
}
interface IBasicDiscounts {
    generateDiscountCodeForBasicUsers(userCode: string): string;
}
interface IProducts extends ISpecialDiscounts, IBasicDiscounts {
    id: number;
    name: string;
    readonly code: string;
    price: number;
    productType: number;
}
Листинг 19. Расширение интерфейса
class Electronics implements IProducts {
    id: number;
    name: string;
    code: string;
    price: number;
    productType: number;
    constructor() {
    this.code = "Some unique code for every product";
    }
    generateDiscountCodeForVIPUsers(userCode: string): string {
        return `SPCODE${userCode}`;
    }
    generateDiscountCodeForBasicUsers(userCode: string): string {
        return `BSCODE${userCode}`;
    }
}