typescript中的類型兼容性


函數兼容性

函數參數個數

比如有如下兩個函數:

let x = (a: number) => 0
let y = (b: number, c: string) => 0

函數參數個數如果要兼容,需要滿足條件:如果對函數 y 進行賦值,則 x 中的每個參數都應在 y 中有對應,也就是 x 的參數個數小於 y 的參數個數。所以有如下結果:

y = x // 沒問題
x = y // error,不能將類型“(b: number, c: string) => number”分配給類型“(a: number) => number”。

所以在函數兼容性中,參數個數少的可以賦值給參數個數多的。

 

函數參數類型和返回值類型

除了參數個數,參數的類型也需要對應,有如下三個函數:

let x = (a: number) => 0
let y = (b: string) => 0
let z = (c: string) => false

x 和 y 兩個函數的參數個數和返回值都相同,但是參數類型對應不上,所以互相不兼容:

x = y // error,不能將類型“(b: string) => number”分配給類型“(a: number) => number”。參數“b”和“a” 的類型不兼容。

y 和 z 兩個函數的參數個數和參數類型都相同,但是返回值類型對應不上,所以互相不兼容:

y = z // error,不能將類型“(c: string) => boolean”分配給類型“(b: string) => number”。不能將類型“boolean”分配給類型“number”。

我們來改造下 y 函數,如下:

let y = (b: string): number | boolean => 0
let z = (c: string) => false

y = z // 沒問題

y 函數改造后返回聯合類型 number | boolean ,所以可以將 z 賦值給 y。

 

剩余參數和可選參數

當要被賦值的函數參數中包含剩余參數(...args)時,賦值的函數可以用任意個數的參數代替,但類型要對應,如下:

let x = (...args: number[]):number => 0

let y = (a: number, b: number) => a + b

let z = () => 0

x = y // 沒問題
x = z // 沒問題

x 函數中參數是 ...args,表示可以有 0 個參數,也可以有無限多個參數,所以 y 和 z 函數都可以賦值給 x,前提是參數類型是一樣的。

 

剩余參數可以看成無數個可選參數,來看個可選參數和剩余參數結合的例子:

const getNum = (
  arr: number[], // 函數第一個參數是一個各項 number 類型的數組
  callback: (arg1: number, arg2?: number) => number // 第二個參數是函數,它有一個或者兩個參數
): number => {
  return callback(...arr) // 應有 1-2 個參數,但獲得的數量大於等於 0
}

例子中函數實體里 callback(...arr) 實現了參數中的 callback,callback 參數個數應為 1 個或者 2 個,但是 ...arr 表示參數個數可能是 0 個或者是無限個,當為 0 個時就會不兼容報錯。改成下面這樣就沒問題了:

return callback(arr[0], ...arr)

 

函數參數雙向協變 

函數參數雙向協變即參數類型無需絕對相同,比如:

let A = function(arg: number|string): void{}
let B = function(arg: number): void{}

A = B // 可以

B = A // 可以

這個例子中,A 和 B 的參數類型並不是完全一樣的,A 的參數類型是一個聯合類型 number | string,而 B 的參數類型是 number | string 中的 number,這兩個函數兼容。但需要注意的是在嚴格模式下,A = B 不通過,B = A 通過。嚴格模式的開啟是在 tsconfig.json 文件中去掉  "strict": true  的注釋。

 

函數重載

帶有重載的函數,要求被賦值的函數的每個重載都能在用來賦值的函數上找到對應的簽名,例如:
function funcA(arg1: number, arg2: number): number;
function funcA(arg1: string, arg2: string): string;
function funcA(arg1: any, arg2: any): any{
    return arg1 + arg2
}

function funcB(arg1: number, arg2: number): number;
function funcB(arg1: any, arg2: any): any{
    return arg1 + arg2
}

let fn = funcA
fn = funcB // error,不能將類型“(arg1: number, arg2: number) => number”分配給類型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”

例子中 funcB 的重載缺少參數都為 string 類型,返回值為 string 類型的情況,與函數 funcA 不兼容。

 

枚舉

數字枚舉成員類型與數字類型兼容,比如:

enum Status{
    On,
    Off
}

let s = Status.On

s = 2
s = 3

Status.On 的值為 0,數字枚舉成員類型和數值類型互相兼容,所以 s 可以賦值為數字類型。

 

但是不同枚舉類型之間是不兼容的:

enum Status{
    On,
    Off
}

enum Color{
    White,
    Black
}

let s = Status.On

s = Color.White  // error,不能將類型“Color.White”分配給類型“Status”

雖然 Status.On 和 Color.White 的值都為 0,但是它們不兼容。

 

字符串枚舉成員類型與字符串類型不兼容,比如:

enum Status{
    On = 'on',
    Off = 'off'
}

let s = Status.On

s = 'hi' // error,不能將類型“"hi"”分配給類型“Status”

 

比較兩個類類型值的兼容性時,只比較實例的成員,類的靜態成員和構造函數不進行比較:

class Animal{
    static age: number // 靜態屬性 age 是 number 類型
    constructor(public name: string){}
}

class People{
    static age: string // 靜態屬性 age 是 string 類型
    constructor(public name: string){}
}

class Food{
    constructor(public name: number){} // name 是 number 類型
}

let a: Animal
let p: People
let f: Food

a = p // 通過

a = f // error,不能將類型“Food”分配給類型“Animal”。屬性“name”的類型不兼容。

Animal 和 People 的靜態屬性 age 的類型雖然不相同,但是比較兼容性時不比較靜態屬性,而它們都有實例屬性 name,且都為 string 類型,所以 a = p 可行。Food 的實例屬性 name 是 number 類型,所以 a = f 錯誤。

 

類的私有成員和受保護成員會影響兼容性。比較兩個類的兼容性時,當發現有私有成員或者受保護成員時,只有這些成員來自同一個類時才能兼容,即允許子類賦值給父類:

class Parent{
    private age: number
    constructor(){}
}

class Children extends Parent{
    constructor(){
        super()
    }
}

class Other{
    private age: number
    constructor(){}
}

const children: Parent = new Children()

const other: Parent = new Other() // error,不能將類型“Other”分配給類型“Parent”。類型具有私有屬性“age”的單獨聲明。
children 的類型我們指定為了 Parent 類類型,然后給它賦值為 Children 類的實例,沒有問題,是因為 Children 類繼承 Parent 類,且實例屬性沒有差異,而 Other 類則不可以。受保護成員同理。
 

泛型

因為TypeScript是結構性的類型系統,類型參數只影響使用其做為類型一部分的結果類型。比如:

interface Data<T>{}

let data1: Data<string>
let data2: Data<number>

data1 = data2 // 通過

雖然兩次類型參數 T 的類型不一樣,但是因為接口里並沒有用到參數 T,所以不管傳入什么類型都不影響,兩者兼容。相反如果接口中用到參數 T,則結果就相反:

interface Data<T>{
    data: T
}

let data1: Data<string>
let data2: Data<number>

data1 = data2 // error,不能將類型“Data<number>”分配給類型“Data<string>”。不能將類型“number”分配給類型“string”。

 

以上就是類型兼容性的相關知識點。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM