Typecasting

Typecasting - привидение типа (англ. type conversion) — это преобразование значения одного типа в значение другого типа. В Javascript (ECMAScript) это делается автоматически по необходимости с использованием вспомогательных методов toString() и valueOf().

TypeScript вы, скорее всего, используете именно из-за его основной фичи: статическая типизация. Так как TypeScript - это все же тот же Javascript, а вы, скорее всего, Frontend разработчик, то вы не можете жить с переменными строго одного типа. И ваш код пестрит различными мультитипами или периодически вы пытаетесь приводить один тип к другому. Иногда это усложняет вам работу, так как вы можете наталкиваться на "непонятное" для вас поведение компилятора.

Простой пример: вы захотели иметь переменную которая может быть строкой либо массивом строк. Вы считаете что это ок и вам ну очень нужно такое поведение. Окей, TypeScript позволяет описать такой тип следующим образом:

type arOrStr = string[] | string;

let someVar :arOrStr = 'abc';

someVar = 123;

Вроде бы все логично, определили перменную которая или строка или массив строк. Все корректно. Теперь рассмотрим пример некоторой логики, где вы можете использовать такой тип:

type arOrStr = string[] | string;

function foo(a :arOrStr) {
    if (typeof a === 'string') {
        a = [a];
    }
    a.map( x => x.toUpperCase() );
}

foo('abcdef');
foo(['abc', 'def']);

Ожидаемая логика: если пришла строка, то я преобразую переменную в массив из одного элемента с этой строкой. Но вот компилятор упорно ругается. Во-первых, он говорит вам, что есть несоответствие типов (хотя вы-то думаете что все логично):

Error:(9, 3) TS2322: Type '(string[] | string)[]' is not assignable to type 'string[] | string'. Type '(string[] | string)[]' is not assignable to type 'string'.

Во-вторых, он говорит вам, что у перменной данного типа нет метода map(). НО как же так? Ведь у массива есть такой тип. Вот незадача. А что тут не так? На самом деле компилятор делает вам добро! Посмотрите на код внимательнее, тут нет защиты от того, что в рантайме не придет вместо строки что-то другое. Проверка типов только на стадии компиляции, но не на стадии выполнения. В таком случае вы можете сказать, что тогда можно переписать код таким образом:

function foo(a :arOrStr) {

    if (typeof a === 'string') {
        a = [a];
    }

    // Гарантируем, что здесь будет только 100% массив и ничто другое
    if (Array.isArray(a)) {
        a.map(x => x.toUpperCase());
    }
}

Все, теперь компилятор знает про метод map() в нашей перменной, так как мы ему гарантируем что в данном случае у нас будет массив. Но он продолжает ругаться на то, что нельзя присвоить a = [a]. Почему?

Потому, что если расписать данную ситуация через описания типов, то мы получаем следующую картину:

var a :arOrStr[]

что можно переписать как

var a :(string[] | string) []

или

var a: Array<string[] | string>

Т.е. у нас идет попытка присвоить результат другого типа. И с точки зрения компилятора все логично и верно. Это мы, армия JS разработчиков, привыкли что можно так делать и это одно и тоже. Нет, с точки зрения статической типизации - это разные типы. И так, что делать?

Самый простой способ - это завести новую перменную другого типа (проще any) и уже ей присвоить результат:

function foo(a :arOrStr) {
    let b :string[];

    if (!Array.isArray(a)) {
        b = [a];
    }    
    else {
        b = a;
    }

    b.map(x => x.toUpperCase());
}

Обратит внимание, в таком описании нам уже не надо помещать вызов b.map() в условие. Но тут появляются риски ошибок в рантайме. На уровне статического анализа компилятор не позволит просунуть внутрь функции что-то отличное от массива и строки, но в рантайме... Так что это не самый лучший способ.

Кастуем типы

Те, кто разрабатывали или пишут на PHP, знают, что там есть такие вот конструкции:

<?php

$a = "123abc";
$b = (int) $a;

В данном случае мы произвели приведение типа (кастинг). В TypeScript есть такой же способ кастовать типы. Теперь перепишем наш пример с применением преобразования:

function foo(a :string[] | string) {

    if (!Array.isArray(a)) {
        a = <string[]> [a];
    }

    a.map( x => x.toUpperCase() );
}

Теперь компилятор пропускает запись переопределения перменной a=[a]. Но вне этого условия статический анализатор ругается на то, что у нас перменная a такого типа, у которого не может быть метода map(), хоть выше мы явно указали, что если пришло значение отличное от массива, то всеравно преобразуем в массив. В данном случае есть два пути выхода. Первый, добавить еще одну проверку на то, что у нас массив:

function foo(a :string[] | string) {

    if (!Array.isArray(a)) {
        a = <string[]> [a];
    }

    if (Array.isArray(a)) {
        a.map(x => x.toUpperCase());
    }
}

Но в итоге будет скомпилирован код с двумя условиями, а вы точно понимаете, что хоть в рантайме придет все что угодно, но оно всеравно будет приведено к массиву. Хоть это будет и массив чисел или массив объектов, все же это будет массив. Тогда мы можем применить каст перед вызовом метода, просто указав компилятору, что мы понимаем, что мы делаем:

function foo(a :string[] | string) {

    if (!Array.isArray(a)) {
        a = <string[]> [a];
    }

    (<string[]> a).map( x => x.toUpperCase() );
}

Если говорить простым языком, мы попросили заткнуться компилятор и верить нам на слово.

results matching ""

    No results matching ""