1. 程式人生 > >Typescript 實戰 --- (7)型別相容性

Typescript 實戰 --- (7)型別相容性

ts 允許型別相容的變數相互賦值,這個特性增加了語言的靈活性   當一個 型別Y 可以被賦值給另一個 型別X 時,就可以說型別X相容型別Y。其中,X被稱為“目標型別”,Y被稱為“源型別”
X相容Y : X(目標型別) = Y(源型別)

 

1、結構之間相容:成員少的相容成員多的   基本規則是,如果 X 要相容 Y,那麼 Y 至少具有與 X 相同的屬性
interface Named {
  name: string;
}

let x: Named;
let y = { name: 'Chirs', age: 23 };

x = y;
console.log('x', x);   // x { name: 'Chirs', age: 23 }

// 這裡要檢查 y 是否可以賦值給 x,編譯器檢查 x 中的每個屬性,看能否在 y 中也找到對應的屬性

// 相反,把 y 賦值給 x 就會報錯,因為 x 不具備 age 屬性

y = x;  // Property 'age' is missing in type 'Named' but required in type '{ name: string; age: number; }'

 

1-1、子型別賦值
let s: string = 'hello';

s = null; // 由於在 ts 中, null 是所有型別的子型別,也就是說 字元型別相容null型別,所以可以賦值

 

1-2、介面相容性
interface X {
  a: any;
  b: any;
}

interface Y {
  a: any;
  b: any;
  c: any;
}

let x: X = { a: 1, b: '2' }
let y: Y = { a: 3, b: 4, c: 5 }

// 只要源型別y 具備了 目標型別x 的所有屬性,就可以認為 x 相容 y
x = y;
console.log('x', x);   // x { a: 3, b: 4, c: 5 }

 

2、函式之間相容:引數多的相容引數少的   需要判斷函式之間是否相容,常見於兩個函式相互賦值的情況下,也就是函式作為引數的情況   2-1、如果要目標函式相容源函式,需要同時滿足三個條件:   (1)、引數個數:目標函式的個數 多餘 源函式的個數
interface Handler {
  (x: number, y: number): void
}

function foo(handler: Handler) {  // handler:目標函式
  return handler
}

let h1 = (a: number) => {}  // h1:源函式
// 目標函式的引數個數2個 > 源函式引數個數1個
foo(h1);

let h2 = (a: number, b: number, c: number) => {}  // h1:源函式
// 目標函式的引數個數2個 < 源函式引數個數3個
foo(h2);  // 型別“(a: number, b: number, c: number) => void”的引數不能賦給型別“Handler”的引數

 

(2)、引數型別:引數型別必須要匹配
interface Handler {
  (x: number, y: number): void
}

function foo(handler: Handler) {  // handler:目標函式
  return handler
}

let h3 = (a: string) => {}  // h3:源函式

// 儘管目標函式的引數個數多餘源函式的引數個數,但是引數型別不同
foo(h3); 

/*
  報錯資訊:
  型別“(a: string) => void”的引數不能賦給型別“Handler”的引數
  引數“a”和“x” 的型別不相容
  不能將型別“number”分配給型別“string”
*/
interface Point3D {
  x: number;
  y: number;
  z: number;
}

interface Point2D {
  x: number;
  y: number;
}

// 函式 p3d 和 p2d 的引數個數都是1,引數型別都是物件
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}

// 賦值時,依然採用的是目標函式的引數個數必須大於源函式引數個數,且引數型別相同的原則
p3d = p2d;
p2d = p3d; // 想要不報錯,需要關閉 tsconfig.json 中的一個配置  strictFunctionTypes

 

函式的引數之間可以相互賦值的情況,稱為 “函式引數雙向協變”。它允許把一個精確的型別,賦值給一個不那麼精確的型別,這樣就不需要把一個不精確的型別斷言成一個精確的型別了   (3)、返回值型別:目標函式的返回值型別必須與源函式的返回值型別相同,或為其子型別
let p = () => ({ name: 'Bob' })
let s = () => ({ name: 'Bob', age: 23 })

// p 作為目標函式,s 作為源函式時,目標函式的返回值是源函式返回值的子型別
p = s;
s = p;  // 不能將型別“() => { name: string; }”分配給型別“() => { name: string; age: number; }”
  2-2、關於固定引數、可選引數和剩餘引數之間的相容   1)、固定引數可以相容可選引數和剩餘引數 2)、可選引數不相容固定引數和剩餘引數 3)、剩餘引數可以相容固定引數和剩餘引數
// 固定引數
let a = (x: number, y: number) => {};
// 可選引數
let b = (x?: number, y?: number) => {};
// 剩餘引數
let c = (...args: number[]) => {};

// 固定引數 相容 可選引數和剩餘引數
a = b;
a = c;

// 可選引數 不相容 固定引數和剩餘引數 (可將 strictFunctionTypes 設為false 實現相容)
b = a;
b = c;

// 剩餘引數 相容 固定引數和可選引數
c = a;
c = b;

 

2-3、函式過載   對於有過載的函式,源函式的每個過載都要在目標函式上找到對應的函式簽名,這樣確保了目標函式可以在所有源函式可呼叫的地方地方
// 源函式
function overload(x: number, y: number): number;
function overload(x: string, y: string): string;

// 目標函式
function overload(x: any, y: any): any{ };
// Error1: 目標函式的引數個數 少於 源函式的引數
// 源函式
function overload(x: number, y: number): number;  
// This overload signature is not compatible with its implementation signature
function overload(x: string, y: string): string;

// 目標函式
function overload(x: any, y: any, z: any): any{ };


// Error2: 目標函式和源函式的返回值型別不相容
// 源函式
function overload(x: number, y: number): number;  
// This overload signature is not compatible with its implementation signature
function overload(x: string, y: string): string;

// 目標函式
function overload(x: any, y: any) { };

 

3、列舉型別的相容性   (1)、列舉型別和數字型別相互相容 (2)、列舉型別之間是完全不相容的
enum Color { Red, Green, Pink };
enum Fruit { Apple, Banana, Orange };

// 列舉型別和數字型別相互相容

let fruit: Fruit.Apple = 4;
let num: number = Color.Red;

// 相同列舉型別之間不相容
let c: Color.Green = Color.Red;
// 不能將型別“Color.Red”分配給型別“Color.Green”

// 不同列舉型別之間不相容

let color: Color.Pink = Fruit.Orange;
// 不能將型別“Fruit.Orange”分配給型別“Color.Pink”

 

4、類相容性   (1)、靜態成員和建構函式是不參與比較的,如果兩個類具有相同的例項成員,那他們的例項則可以相容
class A {
  id: number = 1;
  constructor(p: number, q: number) {}
}

class B {
  static s: number = 1;
  id: number = 2;
  constructor(p: number) {}
}

let aa = new A(3, 6);
let bb = new B(8);

// 兩個類都含有相同的例項成員 number 型別的id,儘管建構函式不同,依然相互相容
aa = bb;
bb == aa;

 

(2)、如果兩個類中含有相同的私有成員,他們的例項不相容,但是父類和子類的例項可以相互相容
class A {
  id: number = 1;
  private name: string = 'hello';
  constructor(p: number, q: number) {}
}

class B {
  static s: number = 1;
  id: number = 2;
  private name: string = 'hello';
  constructor(p: number) {}
}

let aa = new A(3, 6);
let bb = new B(8);

// 在上例的基礎上各自添加了相同的 私有成員name,就無法相容了
aa = bb;
bb == aa;

// 均報錯:不能將型別“B”分配給型別“A”,型別具有私有屬性“name”的單獨宣告
class A {
  id: number = 1;
  private name: string = 'hello';
  constructor(p: number, q: number) {}
}

class SubA extends A {}

let aa = new A(3, 6);
let child = new SubA(1, 2)

// 就算包含私有成員屬性,但是父類和子類的例項可以相互相容
aa = child;
child == aa;

 

5、泛型相容性   (1)、如果兩個泛型的定義相同,但是沒有指定泛型引數,它們之間也是相互相容的;
// demo 1
interface Empty<T> {};

let a: Empty<string> = {};
let b: Empty<number> = {};

a = b;
b = a;


// demo 2
let log1 = <T>(x: T): T => {
  console.log('x');
  return x
}

let log2 = <U>(y: U): U => {
  console.log('y');
  return y;
}

log1 = log2;

 

(2)、如果泛型中指定了型別引數,會按照結果型別進行比較;
interface NotEmpty<T> {
  value: T;
};

let a: NotEmpty<string> = {
  value: 'string'
};
let b: NotEmpty<number> = {
  value: 123
};

a = b; // 不能將型別“NotEmpty<number>”分配給型別“NotEmpty<string>”