Type Compatibility
Type Compatibility
Сумісність типу (як ми обговорюємо тут) визначає, чи можна призначити один предмет іншому. наприклад 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";
// Later
foo.toPrecision(3); // Allowed as you typed it as `any`Structural
Об'єкти TypeScript є структурно типізованими. Це означає, що іменуванняs не мають значення, якщо структури збігаються
interface Point {
    x: number,
    y: number
}
class Point2D {
    constructor(public x:number, public y:number){}
}
let p: Point;
// OK, because of structural typing
p = new Point2D(1,2);Це дозволяє вам створювати об’єкти на льоту (як ви це робите у vanilla JS) і залишатися в безпеці, коли це можна зробити.
Також більше даних вважається правильним
interface Point2D {
    x: number;
    y: number;
}
interface Point3D {
    x: number;
    y: number;
    z: number;
}
var point2D: Point2D = { x: 0, y: 10 }
var point3D: Point3D = { x: 0, y: 10, z: 20 }
function iTakePoint2D(point: Point2D) { /* do something */ }
iTakePoint2D(point2D); // exact match okay
iTakePoint2D(point3D); // extra information okay
iTakePoint2D({ 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 */
interface Point2D { x: number; y: number; }
interface Point3D { x: number; y: number; z: number; }
/** Two sample functions */
let iMakePoint2D = (): Point2D => ({ x: 0, y: 0 });
let iMakePoint3D = (): Point3D => ({ x: 0, y: 0, z: 0 });
/** Assignment */
iMakePoint2D = iMakePoint3D; // Okay
iMakePoint3D = iMakePoint2D; // ERROR: Point2D is not assignable to Point3DNumber of arguments
Менша кількість аргументів — це добре (тобто функції можуть ігнорувати додаткові параметри). Адже вам гарантовано викличете їх із як мінімум достатніми аргументами.
let iTakeSomethingAndPassItAnErr
    = (x: (err: Error, data: any) => void) => { /* do something */ };
iTakeSomethingAndPassItAnErr(() => null) // Okay
iTakeSomethingAndPassItAnErr((err) => null) // Okay
iTakeSomethingAndPassItAnErr((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 (будь-яка кількість аргументів) сумісні, знову ж для зручності.
let foo = (x:number, y: number) => { /* do something */ }
let bar = (x?:number, y?: number) => { /* do something */ }
let bas = (...args: number[]) => { /* do something */ }
foo = bar = bas;
bas = bar = foo;Примітка: необов’язковий (у нашому прикладі
bar) і необов’язковий (у нашому прикладіfoo) сумісні, лише якщо strictNullChecks має значення false.
Types of arguments
bivariant : це розроблено для підтримки типових сценаріїв обробки подій
/** Event Hierarchy */
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
/** Sample event listener */
enum EventType { Mouse, Keyboard }
function addEventListener(eventType: EventType, handler: (n: Event) => void) {
    /* ... */
}
// Unsound, but useful and common. Works as function argument comparison is bivariant
addEventListener(EventType.Mouse, (e: MouseEvent) => console.log(e.x + "," + e.y));
// Undesirable alternatives in presence of soundness
addEventListener(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 types
addEventListener(EventType.Mouse, (e: number) => console.log(e));Крім того, Array<Child> можна призначити Array<Base> (коваріація), оскільки функції сумісні. Коваріація масиву вимагає, щоб усі функції Array<Child> можна було призначити Array<Base>, наприклад. push(t:Child) можна призначити push(t:Base), що стало можливим завдяки біваріантності аргументів функції.
Це може збити з пантелику людей, які знають інші мови, які очікували б наступного помилки, але не в TypeScript:
/** Type Hierarchy */
interface Point2D { x: number; y: number; }
interface Point3D { x: number; y: number; z: number; }
/** Two sample functions */
let iTakePoint2D = (point: Point2D) => { /* do something */ }
let iTakePoint3D = (point: Point3D) => { /* do something */ }
iTakePoint3D = iTakePoint2D; // Okay : Reasonable
iTakePoint2D = iTakePoint3D; // Okay : WHATEnums
Enum сумісні з числами, а числа сумісні з enum.
enum Status { Ready, Waiting };
let status = Status.Ready;
let num = 0;
status = num; // OKAY
num = status; // OKAY** Значення Enum з різних типів enum вважаються несумісними. Це робить переліки придатними для використання номінально (на відміну від структурних)
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
let color = Color.Red;
status = color; // ERRORClasses
Порівнюються лише члени екземпляра та методи.constructors і statics не грають ролі.
class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { /** do something */ }
}
class Size {
    feet: number;
    constructor(meters: number) { /** do something */ }
}
let a: Animal;
let s: Size;
a = s;  // OK
s = a;  // OKprivateтаprotectedчлени повинні походити з того самого класу. Такі члени по суті роблять клас nominal.
/** A class hierarchy */
class Animal { protected feet: number; }
class Cat extends Animal { }
let animal: Animal;
let cat: Cat;
animal = cat; // OKAY
cat = animal; // OKAY
/** Looks just like Animal */
class Size { protected feet: number; }
let size: Size;
animal = size; // ERROR
size = animal; // ERRORGenerics
Оскільки TypeScript має структурну систему типів, параметри типу впливають на сумісність лише тоді, коли вони використовуються членом. Наприклад, у наступному T не впливає на сумісність:
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y;  // okay, y matches structure of xОднак, якщо використовується T, він відіграватиме роль у сумісності на основі свого екземпляру, як показано нижче:
interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y;  // error, x and y are not compatibleУ випадках, коли загальні аргументи не були інстанцировані, вони замінюються на any перед перевіркою сумісності:
let identity = function<T>(x: T): T {
    // ...
}
let reverse = function<U>(y: U): U {
    // ...
}
identity = reverse; // Добре, тому що ((x: any)=>any відповідає (y: any)=>anyУніверсали, що включають класи, відповідають відповідній сумісності класів, як згадувалося раніше
class List<T> {
  add(val: T) { }
}
class Animal { name: string; }
class Cat extends Animal { meow() { } }
const animals = new List<Animal>();
animals.add(new Animal()); // Okay 
animals.add(new Cat()); // Okay 
const cats = new List<Cat>();
cats.add(new Animal()); // Error 
cats.add(new Cat()); // OkayFootNote: Invariance
Ми сказали, що інваріантність — це єдиний правильний варіант. Ось приклад, коли дисперсія contra і co небезпечна для масивів.
/** Hierarchy */
class Animal { constructor(public name: string){} }
class Cat extends Animal { meow() { } }
/** An item of each */
var animal = new Animal("animal");
var cat = new Cat("cat");
/**
 * Demo : polymorphism 101
 * Animal <= Cat
 */
animal = cat; // Okay
cat = animal; // ERROR: cat extends animal
/** Array of each to demonstrate variance */
let animalArr: Animal[] = [animal];
let catArr: Cat[] = [cat];
/**
 * Obviously Bad : Contravariance
 * Animal <= Cat
 * Animal[] >= Cat[]
 */
catArr = animalArr; // Okay if contravariant
catArr[0].meow(); // Дозволено, але BANG 🔫 під час виконання
/**
 * Also Bad : covariance
 * Animal <= Cat
 * Animal[] <= Cat[]
 */
animalArr = catArr; // Okay if covariant
animalArr.push(new Animal('another animal')); // Just pushed an animal into catArr!
catArr.forEach(c => c.meow()); // Дозволено, але BANG 🔫 під час виконанняLast updated