Сумісність типу (як ми обговорюємо тут) визначає, чи можна призначити один предмет іншому. наприклад string i number несумісні:
let str:string="Hello";let num:number=123;str = num; // ERROR: `number` is not assignable to `string`num = str; // ERROR: `string` is not assignable to `number`
Soundness
Система типів TypeScript розроблена так, щоб бути зручною та допускати unsound поведінку, напр. будь-що можна призначити будь-якому, що означає, що компілятор повинен дозволити вам робити все, що завгодно:
let foo:any=123;foo ="Hello";// Laterfoo.toPrecision(3); // Allowed as you typed it as `any`
Structural
Об'єкти TypeScript є структурно типізованими. Це означає, що іменуванняs не мають значення, якщо структури збігаються
interfacePoint { x:number, y:number}classPoint2D {constructor(public x:number,public y:number){}}let p:Point;// OK, because of structural typingp =newPoint2D(1,2);
Це дозволяє вам створювати об’єкти на льоту (як ви це робите у vanilla JS) і залишатися в безпеці, коли це можна зробити.
Також більше даних вважається правильним
interfacePoint2D { x:number; y:number;}interfacePoint3D { x:number; y:number; z:number;}var point2D:Point2D= { x:0, y:10 }var point3D:Point3D= { x:0, y:10, z:20 }functioniTakePoint2D(point:Point2D) { /* do something */ }iTakePoint2D(point2D); // exact match okayiTakePoint2D(point3D); // extra information okayiTakePoint2D({ x:0 }); // Error: missing information `y`
Variance
Дисперсія — це легке для розуміння та важливе поняття для аналізу сумісності типів.
Для простих типів Base і Child, якщо Child є дочірнім типом Base, тоді екземпляри Child можна призначити змінній типу Base.
Це поліморфізм 101
Сумісність типів складних типів, що складаються з таких типів Base і Child, залежить від того, де Base і Child у подібних сценаріях керуються variance.
Коваріант: (співпраця, він же спільний) тільки в спільному напрямку
Контраваріант: (контра, він же негативний) тільки вoпротилежний напрямок
Біваріант : (bi або обидва) і co, і contra.
Invariant : якщо типи не зовсім однакові, вони несумісні.*
Примітка: для повністю надійної системи за наявності змінних даних, таких як JavaScript, invariant є єдиним дійсним варіантом. Але, як уже згадувалося, зручність змушує нас робити необґрунтований вибір.
Functions
Порівнюючи дві функції, слід враховувати кілька тонких моментів.
Return Type
covariant: тип повернення має містити принаймні достатньо даних.
/** Type Hierarchy */interfacePoint2D { x:number; y:number; }interfacePoint3D { x:number; y:number; z:number; }/** Two sample functions */letiMakePoint2D= ():Point2D=> ({ x:0, y:0 });letiMakePoint3D= ():Point3D=> ({ x:0, y:0, z:0 });/** Assignment */iMakePoint2D = iMakePoint3D; // OkayiMakePoint3D = iMakePoint2D; // ERROR: Point2D is not assignable to Point3D
Number of arguments
Менша кількість аргументів — це добре (тобто функції можуть ігнорувати додаткові параметри). Адже вам гарантовано викличете їх із як мінімум достатніми аргументами.
let iTakeSomethingAndPassItAnErr= (x: (err:Error, data:any) =>void) => { /* do something */ };iTakeSomethingAndPassItAnErr(() =>null) // OkayiTakeSomethingAndPassItAnErr((err) =>null) // OkayiTakeSomethingAndPassItAnErr((err, data) =>null) // Okay// ERROR: Argument of type '(err: any, data: any, more: any) => null' is not assignable to parameter of type '(err: Error, data: any) => void'.iTakeSomethingAndPassItAnErr((err, data, more) =>null);
Optional and Rest Parameters
Необов’язкові (попередньо визначена кількість) і параметри Rest (будь-яка кількість аргументів) сумісні, знову ж для зручності.
letfoo= (x:number, y:number) => { /* do something */ }letbar= (x?:number, y?:number) => { /* do something */ }letbas= (...args:number[]) => { /* do something */ }foo = bar = bas;bas = bar = foo;
Примітка: необов’язковий (у нашому прикладі bar) і необов’язковий (у нашому прикладі foo) сумісні, лише якщо strictNullChecks має значення false.
Types of arguments
bivariant : це розроблено для підтримки типових сценаріїв обробки подій
/** Event Hierarchy */interfaceEvent { timestamp:number; }interfaceMouseEventextendsEvent { x:number; y:number }interfaceKeyEventextendsEvent { keyCode:number }/** Sample event listener */enumEventType { Mouse, Keyboard }functionaddEventListener(eventType:EventType,handler: (n:Event) =>void) {/* ... */}// Unsound, but useful and common. Works as function argument comparison is bivariantaddEventListener(EventType.Mouse, (e:MouseEvent) =>console.log(e.x +","+e.y));// Undesirable alternatives in presence of soundnessaddEventListener(EventType.Mouse, (e:Event) =>console.log((<MouseEvent>e).x +","+ (<MouseEvent>e).y));addEventListener(EventType.Mouse, <(e:Event) =>void>((e:MouseEvent) =>console.log(e.x +","+e.y)));// Still disallowed (clear error). Type safety enforced for wholly incompatible typesaddEventListener(EventType.Mouse, (e:number) =>console.log(e));
Крім того, Array<Child> можна призначити Array<Base> (коваріація), оскільки функції сумісні. Коваріація масиву вимагає, щоб усі функції Array<Child> можна було призначити Array<Base>, наприклад. push(t:Child) можна призначити push(t:Base), що стало можливим завдяки біваріантності аргументів функції.
Це може збити з пантелику людей, які знають інші мови, які очікували б наступного помилки, але не в TypeScript:
/** Type Hierarchy */interfacePoint2D { x:number; y:number; }interfacePoint3D { x:number; y:number; z:number; }/** Two sample functions */letiTakePoint2D= (point:Point2D) => { /* do something */ }letiTakePoint3D= (point:Point3D) => { /* do something */ }iTakePoint3D = iTakePoint2D; // Okay : ReasonableiTakePoint2D = iTakePoint3D; // Okay : WHAT
Enums
Enum сумісні з числами, а числа сумісні з enum.
enumStatus { Ready, Waiting };let status =Status.Ready;let num =0;status = num; // OKAYnum = status; // OKAY
** Значення Enum з різних типів enum вважаються несумісними. Це робить переліки придатними для використання номінально (на відміну від структурних)
enumStatus { Ready, Waiting };enumColor { Red, Blue, Green };let status =Status.Ready;let color =Color.Red;status = color; // ERROR
Classes
Порівнюються лише члени екземпляра та методи.constructors і statics не грають ролі.
classAnimal { feet:number;constructor(name:string, numFeet:number) { /** do something */ }}classSize { feet:number;constructor(meters:number) { /** do something */ }}let a:Animal;let s:Size;a = s; // OKs = a; // OK
private та protected члени повинні походити з того самого класу. Такі члени по суті роблять клас nominal.
/** A class hierarchy */classAnimal { protected feet:number; }classCatextendsAnimal { }let animal:Animal;let cat:Cat;animal = cat; // OKAYcat = animal; // OKAY/** Looks just like Animal */classSize { protected feet:number; }let size:Size;animal = size; // ERRORsize = animal; // ERROR
Generics
Оскільки TypeScript має структурну систему типів, параметри типу впливають на сумісність лише тоді, коли вони використовуються членом. Наприклад, у наступному T не впливає на сумісність:
interfaceEmpty<T> {}let x:Empty<number>;let y:Empty<string>;x = y; // okay, y matches structure of x
Однак, якщо використовується T, він відіграватиме роль у сумісності на основі свого екземпляру, як показано нижче:
interfaceNotEmpty<T> { data:T;}let x:NotEmpty<number>;let y:NotEmpty<string>;x = y; // error, x and y are not compatible
У випадках, коли загальні аргументи не були інстанцировані, вони замінюються на any перед перевіркою сумісності:
letidentity=function<T>(x:T):T {// ...}letreverse=function<U>(y:U):U {// ...}identity = reverse; // Добре, тому що ((x: any)=>any відповідає (y: any)=>any
Універсали, що включають класи, відповідають відповідній сумісності класів, як згадувалося раніше