release

3.3

Повышение производительности сборки для команды --build --watch

Так как ссылки на проекты, появившиеся в версии v3.0, способны значительно упростить разработку крупных проектов, команда TypeScript, в версии v3,3, направив свои усилия на усовершенствование этого функционала, смогла ускорить процесс сборки при совместном использовании флагов --build и --watch на 50% - 75%, что действительно можно назвать впечатляющим результатом.

Улучшение поведения для типов Union указанных в качестве параметров функции

До версии v3.3 было невозможно вызвать функцию даже если аргумент, передаваемый при вызове, принадлежал к типу, включенным в определение каждого объединенного типа указанного в аннотации параметров функциональных типов определяющих объединенный вызываемый тип к которому принадлежит вызываемая функция.

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

Всем нам известно, что объединенный тип (Union) представляет из себя множество типов, к одному из которых может принадлежать ассоциированное с ним значение.

ts
// До v3.3

/**
 * Несовместимые типы A, B
 */
interface A {
    a: number;
}
interface B {
    b: number;
}

/**
 * Определение функционального типа параметры которого
 * принадлежат к типу Union определяющегося типами A и B
 */
type T0 = (p: A | B) => number;

/**
 * Декларация функции принадлежащий к функциональному типу T0
 */
declare const f: T0;

f({ a: 0 }); // Ok, аргумент принадлежащий к типу A; const f: (p: A | B) => number
f({ b: 0 }); // Ok, аргумент принадлежащий к типу B; const f: (p: A | B) => number
```

Из кода выше понятно, что компилятор, в случаи первого вызова, определяет принадлежность аргумента к типу `A`, а во втором к типу `B`. Все предельно просто, поэтому перейдем к следующему шагу и расширим пример.

`````ts
// До v3.3

/**
 * Добавлен ещё один тип C
 * Несовместимые типы A, B и C
 */
interface A {
    a: number;
}
interface B {
    b: number;
}
interface C {
    c: number;
}

/**
 * добавлена определение ещё одного функционального типа T1
 * параметры которого принадлежат к типу Union определяющегося
 * типами A и C и также отличным возвращаемым типом
 */
type T0 = (p: A | B) => number;
type T1 = (p: A | C) => string;

/**
 * Теперь декларация функции принадлежит к типу Union
 */
declare const f: T0 | T1;

f({ a: 0 }); // Error, (error text is listed below)
f({ b: 0 }); // Error, (error text is listed below)
f({ c: 0 }); // Error, (error text is listed below)

/**
 * Cannot invoke an expression whose type lacks a call
 * signature. Type 'T0 | T1' has no compatible call
 * signatures.ts(2349)
 */
```

Как видно из кода выше, в случаях, когда вызываемая функция принадлежит к типу `Union` определенному функциональными типами, в параметрах которых также указаны объединенные типы, даже не смотря на то, что их определение включает общий для всех тип `A`, возникает ошибка. Это кажется нелогичным, так как тип объединение вызывающий ошибку определяется типами, которые по отдельности к ней не приводят.

Поэтому начиная с версии _v3.3_ это поведение было изменено и теперь код из второго примера не приводит к ошибке.

`````ts
// Начиная с v3.3

interface A {
    a: number;
}
interface B {
    b: number;
}
interface C {
    c: number;
}

type T0 = (p: A | B) => number;
type T1 = (p: A | C) => string;

declare const f: T0 | T1;

f({ a: 0 }); // Ok, const f: (p: A | (A & C) | (B & A) | (B & C)) => string | number
f({ b: 0 }); // Error, так как тип B определяет объединение указанное только в параметрах типа T0
f({ c: 0 }); // Error, так как тип С определяет объединение указанное только в параметрах типа T1
```

Код выше наглядно демонстрирует сказанное, а именно тот факт, что при вызове функции с аргументом принадлежащем к типу `A`, который является общим для всех определений типов Union, компилятор рассматривает вызываемую функцию основываясь сразу на двух функциональных типах `T0` и `T1` -

`````ts
/**
 * Так компилятор начиная с v3.3 видит вызываемый тип
 */
const f: (p: A | (A & C) | (B & A) | (B & C)) => string | number;
```

Кроме того, что уже известно на данный момент, также существует ещё один случай при котором новое поведение работать не будет. Таким случаем является ограничение на использование функциональных обобщены типов. Новое поведение не будет работать, если объединенный тип определяется более чем одним обобщенным вызываемым типом.

`````ts
/**
 * Тип A является обобщенным типом (generics type)
 */
interface A<T> {
    a: number;
}
interface B {
    b: number;
}
interface C {
    c: number;
}

/**
 * сигнатура обоих типов (T0 и T1) имеют
 * объявления параметров типа (T)
 */
type T0 = <T>(p: A<T> | B) => number;
type T1 = <T>(p: A<T> | C) => string;

declare const f: T0 | T1;

f<number>({ a: 0 }); // Error, (error text is listed below)

/**
 * Cannot invoke an expression whose type lacks a call
 * signature. Type 'T0 | T1' has no compatible call
 * signatures.ts(2349)
 */
```

Как уже было сказано, ошибка возникает из-за того, что оба типа `T0` и `T1` являются обобщенными. Но обобщенные типы в реальной практике обычное явление, при возникновении подобного случая единственных выход прибегнуть к механизму закрытия типазаполнителя.

`````ts
interface A<T> {
    a: number;
}
interface B {
    b: number;
}
interface C {
    c: number;
}

/**
 * Сигнатура только одного типа T0 является обобщенной.
 * В определении типа T1 тип A указан с закрытым параметром типа.
 */
type T0 = <T>(p: A<T> | B) => number;
type T1 = (p: A<number> | C) => string;

declare const f: T0 | T1;

f<number>({ a: 0 }); // Ok, const f: <number>(p: A<number> | (B & A<number>) | (B & C) | (A<number> & C)) => string | number
```

Кроме того, новое поведение отразилось на методе принадлежащему классу `Array`, а именно `forEach`. До версии _v3.3_, в случаях когда операции выполнялись над объектом принадлежащем к объединенному типу определенного массивами принадлежащих к разным типам, итерировать с помощью метода `forEach` над ним можно было лишь после его уточнения.

`````ts
interface Bird {
    kind: 'bird';

    uniqueBirdProp: number;
}
interface Fish {
    kind: 'fish';

    uniqueFishProp: string;
}

function isBirdAll(animalAll: Bird[] | Fish[]): animalAll is Bird[] {
    return animalAll[0].kind === 'bird';
}

/**
 * Параметр принадлежит к объединеному типу определяемого
 * типами массива принадлежащих к типам Bird и Fish
 */
function f(animalAll: Bird[] | Fish[]): void {
    /**
     * Уточнение массива с посмощью защитника типа
     */
    if (isBirdAll(animalAll)) {
        animalAll.forEach((animal) => {
            // param animal is of type Bird
        });
    } else {
        animalAll.forEach((animal) => {
            // param animal is of type Fish
        });
    }
}
```

Начиная с версии _v3.3_ уточнение типа можно выполнять на уровне метода `forEach` -

`````ts
interface Bird {
   kind: "bird";

   uniqueBirdProp: number;
}
interface Fish {
   kind: "fish";

   uniqueFishProp: string;
}



function f( animalAll: Bird[] | Fish[] ): void {
   animalAll.forEach( ( animal: Bird | Fish ) => {
       /**
        * Уточнение типа на уровне метода forEach
        */
       if( animal.kind === 'bird' ) {
           // param animal is of type Bird
       }else{
           // param animal is of type Fish
       }

```