TypeScript: Union Types

TypeScript logoКак известно, в JavaScript отсутствует статическая типизация. Поэтому в переменную или функцию может быть передано значение любого типа. Иногда этим пользуются разработчики, создавая аналог перегрузки методов. Например, в качестве параметра может использоваться или строка, или число или массив строк. Давайте разберемся как написать аналогичный код на TypeScript и, по возможности, со статической типизацией.

Самое простое, но неудобное решение

Самый простой способ – использовать тип any, позволяющий присваивать переменной значения любого типа. В этом случае статической типизации, а значит и отличий от JavaScript, не будет. Даже если разработчик будет знать точный перечень допустимых для присвоения типов. Останется только делать проверки, выполняющиеся во время выполнения приложения.

Объединенные типы

Однако, начиная с TypeScript 1.4 в язык была введена поддержка объединённых типов (union types). В отличии от any, это позволяет ограничить перечень используемых типов и, как следствие, задействовать статическую типизацию. Объявление переменной в этом случае выглядит следующим образом:

var varName: type1 | type2 | ... | typeN;

, где type1…typeN – имена типов.

Разберем использование на примере. Создадим переменную, которая сможет принимать только значения типов string или string[]:

var msg: string | string[];
msg = "Union Types";
msg = ["Union", "Types"];
msg = true; // Ошибка: тип boolean нельзя привести к string или string[] 
msg = [42]; // Ошибка: тип int[] нельзя привести к string или string[]

Строки 4 и 5 не скомпилируются из-за ошибки, т.к. boolean и number[] нельзя привести к указанным в объявлении типам.

Рассмотрим пример простой функции в которую можно передать число, строку или массив строк:

function DisplayMessage(msg: number|string|string[]) : void {
    if (typeof msg === "number") {
        alert("Число: " + msg);
    } else if (typeof msg === "string") {
        alert("Сообщение: " + msg);
    } else {
        msg.forEach(function (m, i) {
            alert(i + ": " + m);
        });
    }    
}

DisplayMessage(42);
DisplayMessage("Hellow World");
DisplayMessage(["Hellow", "World"]);

// Некорректные вызовы:
// DisplayMessage(new Object());
// DisplayMessage([42, 24]);

Вывод конкретных типов из объединенных

Говоря об объединенных типах, необходимо отметить один важный и интересный момент. Компилятор TypeScript понимает конструкции if/else c typeof или instanceof и внутри их блоков рассматривает указанную переменную как имеющую уже конкретный тип. Например, следующий код вызовет ошибку компиляции:

function DisplayMessage(msg: number|string|string[]): void {
    var str = msg.toString();
    if (typeof msg === "number") {
        var fixed1 = msg.toFixed(2);
        alert("Число: " + msg);
    } else if (typeof msg === "string") {
        var fixed2 = msg.toFixed(2); 
        alert("Сообщение: " + msg);
    } else {
        msg.forEach(function (m, i) {
            alert(i + ": " + m);
        });
    }    
}

Проблема в строке 7, где тип msg будет равен string, у которого нет метода toFixed(). При этом аналогичный код в строке 4 корректен, т.к. там msg имеет тип number. В строке 11 тип переменной msg выводится (в блоке else) как оставшийся из всех – string[].

Так же обратите внимание на строку 2. Здесь конкретный тип msg еще не известен. Однако, ошибки не будет, т.к. метод toString() присутствует у всех перечисленных типов.

Добавить комментарий