Кроме типизации, TypeScript пытается сделать жизнь разработчиков более комфортной за счет добавления синтаксического сахара в виде операторов, не существующих в JavaScript мире. Помимо этого, текущая глава поведает о неоднозначных моментах, связанных с уже хорошо известными по JavaScript операторами.
В большинстве языков, в том числе и JavaScript, существует такое понятие как составные операторы присваивания (compound assignment operators), позволяющие совмещать операцию присваивания при помощи оператора = с какой-либо другой допустимой операцией (+-*/! и т.д.) и тем самым значительно сокращать выражения.
123456
leta=1;letb=2;a+=b;// тоже самое что a = a + ba*=b;// тоже самое что a = a * b// и т.д.
Множество существующих операторов совместимы с оператором = за исключением трех часто применяемых операторов, таких как логическое И (&&), логическое ИЛИ (||) и оператор нулевого слияния (??).
123
a=a&&b;a=a||b;a=a??b;
Поскольку дополнительные синтаксические возможности лишь упрощают процесс разработки программ, благодаря комьюнити в TypeScript появился механизм, обозначаемый как операторы присваивания короткого замыкания. Данный механизм позволяет совмещать упомянутые ранее операторы &&, || и ?? непосредственно с оператором присваивания.
123456
leta={};letb={};a&&=b;// a && (a = b)a||=b;// a || (a = b);a??=b;// a !== null && a !== void 0 ? a : (a = b);
Представьте случай, при котором в JavaScript коде вам необходимо удалить у объекта одно из трех определенных в нем полей.
1 2 3 4 5 6 7 8 9101112131415161718
leto={a:0,b:'',c:true,};constf=(o)=>deleteo.b;f(0);// удаляем поле bObject.entries(o).forEach(([key,value])=>console.log(key,value));/** * log - * -> a, 0 * -> b, true */
Задача предельно простая только с точки зрения динамической типизации JavaScript. С точки зрения статической типизации TypeScript, удаление члена объекта нарушает контракт, представляемый декларацией типа. Простыми словами, TypeScript не может гарантировать типобезопасность, пока не может гарантировать существование членов объекта, описанных в его типе.
typeO={a:number;b:string;c:boolean;};leto:O={a:0,b:'',c:true,};constf=(o:O)=>deleteo.b;// [*]f(o);// удаляем поле b/** * [*] Error -> * Oбъект o больше не отвечает * типу O, поскольку в нем нет * обязательного поля b. Поэтому * если дальше по ходу выполнения * программы будут производиться * операции над удаленным полем, * то возникнет ошибка времени выполнения. */
Поэтому TypeScript позволяет удалять члены объекта при помощи оператора delete только в том случае, если они имеют тип any, unknown, never или объявлены как необязательные.
typeT0={field:any;};constf0=(o:T0)=>deleteo.field;// OktypeT1={field:unknown;};constf1=(o:T1)=>deleteo.field;// OktypeT2={field:never;};constf2=(o:T2)=>deleteo.field;// OktypeT3={field?:number;};constf3=(o:T3)=>deleteo.field;// OktypeT4={field:number;};constf4=(o:T4)=>deleteo.field;// Error -> The operand of a 'delete' operator must be optional.
Объявление переменных 'необязательными' при деструктуризации массивоподобных объектов¶
При активном рекомендуемым флаге --noUnusedLocals компилятор выбрасывает ошибки, если переменные, объявленные при деструктуризации массивоподобных объектов, не задействованы в коде.
1 2 3 4 5 6 7 8 9101112131415
functiongetPlayerControlAll(){return[()=>{},()=>{}];}/** * Где-то в другом файле */functionf(){/** * [*] Error -> 'stop' is declared but its value is never read. */let[stop/** [*] */,play]=getPlayerControlAll();returnplay;}
Несмотря на то, что существует способ получать только необходимые значения, это не решит проблему семантики кода, поскольку идентификатор переменной является частью мозаики, иллюстрирующей работу логики. И хотя в TypeScript эту проблему можно решить и другими способами, они ничем не смогут помочь скомпилированному в JavaScript коду.
1 2 3 4 5 6 7 8 91011121314151617181920
functiongetPlayerControlAll(){return[()=>{},()=>{}];}/** * Где-то в другом файле */functionf(){/** * Ошибки больше нет, поскольку первый, неиспользуемый * элемент пропущен. Но, несмотря на это, семантически становится * непонятно, что же возвращает функция getPlayerControlAll(). * * И несмотря на способы, способные решить проблему в TypeScript, * в скомпилированном виде от них не останется и следа. */let[,play]=getPlayerControlAll();returnplay;}
Для таких случаев в TypeScript существует возможность при деструктуризации массивоподобных объектов объявлять переменные как - необязательные. Чтобы переменная расценивалась компилятором как необязательная, её идентификатор должен начинаться с нижнего подчёркивания _identifier.
1 2 3 4 5 6 7 8 91011121314151617
functiongetPlayerControlAll(){return[()=>{},()=>{}];}/** * Где-то в другом файле */functionf(){/** * [*] Ok -> несмотря на то, что переменная stop не * задействована в коде, ошибки не возникает, что позволяет * более глубоко понять логику кода. */let[_stop/** [*] */,play]=getPlayerControlAll();returnplay;}
Модификатор abstract для описания типа конструктора¶
Абстрактные классы предназначены исключительно для расширения (невозможно создать его экземпляр с помощью оператора new), а его абстрактные члены должны обязательно должны быть переопределены потомками.
/** * Абстрактный класс с одним абстрактным методом. */abstractclassShape{abstractgetRectangle():ClientRect;}/** * Из-за того, что класс абстрактный, не получится создать его экземпляр с помощью оператора new. */newShape();// Error -> Cannot create an instance of an abstract class.ts(2511)/** * [0] Кроме этого, подкласс обязательно должен переопределять абстрактные члены своего суперкласса. */classCircleextendsShape{getRectangle(){// [0]return{width:0,height:0,top:0,right:0,bottom:0,left:0,};}}
Но правила, с помощью которых компилятор работает с абстрактными классами, делают типы абстрактных и конкретных конструкторов несовместимыми. Другими словами, абстрактный класс нельзя передать по ссылке, ограниченной более общим типом.
1 2 3 4 5 6 7 8 91011
interfaceIHasRectangle{getRectangle():ClientRect;}typeHasRectangleClass=new()=>IHasRectangle;/** * [*] Type 'typeof Shape' is not assignable to type 'HasRectangleClass'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2322) */letClassType:HasRectangleClass=Shape;// Error [*]
Кроме этого, невозможно получить тип экземпляра абстрактного класса с помощью вспомогательного типа InstanceType<T>.
12345
/** * [*] Type 'typeof Shape' does not satisfy the constraint 'new (...args: any) => any'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2344) */typeInstance=InstanceType<typeofShape>;// Error [*]
Это, в свою очередь, не позволяет реализовать механизм динамического наследования.
1 2 3 4 5 6 7 8 9101112131415161718192021
functionsubclassCreator(Base:new()=>IHasRectangle){returnclassextendsBase{getRectangle(){return{width:0,height:0,top:0,right:0,bottom:0,left:0,};}};}/** * [*] Argument of type 'typeof Shape' is not assignable to parameter of type 'new () => IHasRectangle'. Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345) */subclassCreator(Shape);// Error [*] -> передача в качестве аргумента абстрактного классаsubclassCreator(Circle);// Ok -> передача в качестве аргумента конкретного класса
Для решения этой проблемы в TypeScript существует модификатор abstract, предназначенный для указания в описании типа конструктора.
12345678
interfaceIHasRectangle{getRectangle():ClientRect;}typeHasRectangleClass=abstractnew()=>IHasRectangle;letClassType:HasRectangleClass=Shape;// Ok
Несмотря на то, что тип класса имеет абстрактный модификатор, он также остается совместимым с типами конкретных классов.
1 2 3 4 5 6 7 8 91011121314151617
functionsubclassCreator(Base:abstractnew()=>IHasRectangle){returnclassextendsBase{getRectangle(){return{width:0,height:0,top:0,right:0,bottom:0,left:0};}}}subclassCreator(Shape);// Ok -> абстрактный классsubclassCreator(Circle);// Ok -> конкретный класс
Также с помощью данного оператора можно реализовать собственный вспомогательный тип, позволяющий получить тип экземпляра.
123
typeAbstractInstanceType<Textendsabstractnew(...args:any)=>any>=Textendsnew(...args:any)=>inferR?R:any;typeInstance=AbstractInstanceType<typeofShape>;// Ok