Синтаксические конструкции и операторы

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

Операторы присваивания короткого замыкания (&&=, ||=, &&=)

В большинстве языков, в том числе и JavaScript, существует такое понятие как составные операторы присваивания (compound assignment operators) позволяющие совмещать операцию присваивания при помощи оператора =, с какой-либо другой допустимой операции (+-*/! и т.д.) и тем самым значительно сокращать выражения.

ts
let a = 1;
let b = 2;

a += b; // тоже самое что a = a + b
a *= b; // тоже самое что a = a * b
// и т.д.

Множество существующих операторов совместимы с оператором = за исключением трех, таких часто применяемых операторов, как логическое И (&&), логическое ИЛИ (||) и оператор нулевого слияния (??).

ts
a = a && b;
a = a || b;
a = a ?? b;

Поскольку дополнительные синтаксические возможности лишь упрощают процесс разработки программ, благодаря комьюнити, в TypeScript появился механизм обозначаемый как операторы присваивания короткого замыкания. Данный механизм позволяет совмещать упомянутые ранее операторы &&, || и ?? непосредственно с оператором присваивания.

ts
let a = {};
let b = {};

a &&= b; // a && (a = b)
a ||= b; // a || (a = b);
a ??= b; // a !== null && a !== void 0 ? a : (a = b);

Операнды для delete должны быть необязательными

Представьте случай при котором в JavaScript коде вам необходимо удалить у объекта одно из трех определенных в нем полей.

js
let o = {
    a: 0,
    b: '',
    c: true
};

const f = o => delete o.b;

f(0); // удаляем поле b

Object
    .entries(o)
    .forEach( ([key, value]) => console.log(key, value) );
/**
 * log -
 * -> a, 0
 * -> b, true
 */

Задача предельно простая только с точки зрения динамической типизации JavaScript. С точки зрения статической типизации TypeScript, удаление члена объекта нарушает контракт представляемый декларацией типа. Простыми словами, TypeScript не может гарантировать типобезопасность, пока не может гарантировать существование членов объекта описанных в его типе.

ts
type O = {
    a: number;
    b: string;
    c: boolean;
}

let o: O = {
    a: 0,
    b: '',
    c: true
};

const f = (o: O) => delete o.b; // [*]

f(o); // удаляем поле b

/**
 * [*] Error ->
 * Oбъект o больше не отвечает
 * типу O поскольку в нем нет
 * обязательного поля b. Поэтому
 * если дальше по ходу выполнения
 * программы будут производится
 * операции над удаленным полем,
 * то возникнет ошибка времени выполнения.
 */

Поэтому TypeScript позволяет удалять члены объекта при помощи оператора delete только в том случае, если они имеют тип any, unknown, never или объявлены как необязательные.

ts
type T0 = {
    field: any;
}

const f0 = (o: T0) => delete o.field; // Ok


type T1 = {
    field: unknown;
}

const f1 = (o: T1) => delete o.field; // Ok


type T2 = {
    field: never;
}

const f2 = (o: T2) => delete o.field; // Ok


type T3 = {
    field?: number;
}

const f3 = (o: T3) => delete o.field; // Ok


type T4 = {
    field: number;
}

const f4 = (o: T4) => delete o.field; // Error -> The operand of a 'delete' operator must be optional.