Typescript 實戰 --- (7)型別相容性
阿新 • • 發佈:2020-01-19
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>”