TypeScript - 型別相容性



在 TypeScript 中,型別相容性是指將一種型別的變數、物件等賦值給另一種型別的能力。換句話說,它指的是根據型別的結構檢查兩種型別是否相容的能力。

例如,字串和布林型別彼此不相容,如下面的程式碼所示。

let s:string = "Hello";
let b:boolean = true;
s = b; // Error: Type 'boolean' is not assignable to type 'string'
b = s; // Error: Type 'string' is not assignable to type 'boolean'

TypeScript 的型別系統允許執行編譯時不安全的某些操作。例如,任何型別的變數都與 'any' 型別相容,這是不安全的行為。

例如:

let s: any = 123;
s = "Hello"; // Valid

TypeScript 如何執行型別相容性檢查?

Typescript 使用結構化子型別和結構化賦值來執行型別相容性檢查。讓我們透過示例學習每一個。

結構化子型別

TypeScript 使用結構化子型別方法來檢查特定型別是否為另一種型別的子型別。即使特定型別的成員名稱不匹配,但結構匹配,TypeScript 編譯器也會將這兩種型別視為相同。

例如:

interface Person {
    name: string;
}

let person: Person;
let obj = { name: "John", age: 30 };

// Ok
person = obj;

要檢查 'obj' 物件的型別是否與 Person 介面的型別相容,typescript 會檢查 'obj' 是否至少包含 Person 介面中包含的所有屬性和方法。

TypeScript 不關心新增到子型別的額外成員。這裡,obj 物件包含一個額外的 'age' 屬性,但它仍然與 Person 型別相容,因為 obj 物件包含字串型別的 'name' 屬性。

如果 'obj' 物件不包含 Person 介面的所有成員,則它不能賦值給具有 Person 型別的物件。例如:

interface Person {
    name: string;
}

let person: Person;
let obj = { n: "John", age: 30 };

// Not Ok
person = obj;

當我們編譯上述程式碼時,它會丟擲錯誤,因為 'obj' 物件的型別與 Person 介面不同。

如何有效地使用型別相容性?

開發人員可以使用介面和泛型來有效地在 TypeScript 中使用型別。以下是使用介面和泛型進行型別相容性的最佳技巧。

使用介面

使用介面,開發人員可以定義契約或型別,以確保實現符合這些型別。這有助於確保程式碼不同部分之間的型別相容性。

讓我們透過下面的示例來了解它。

示例

在下面的示例中,'user2' 變數的型別為 'User'。因此,開發人員可以將具有與 'User' 介面相同屬性的物件賦值給 'user2' 物件。

interface User {
    name: string;
    age: number;
}

const user = { name: "Alice", age: 30 };
let user2: User = user;
console.log(user2)

編譯後,它將生成以下 JavaScript 程式碼。

const user = { name: "Alice", age: 30 };
let user2 = user;
console.log(user2);

輸出

其輸出如下:

{ name: 'Alice', age: 30 }

使用泛型

我們可以使用泛型來建立可重用的元件,這些元件可以與多種資料型別一起工作,而不是單一資料型別。它允許開發人員將型別作為引數傳遞,並將其用於變數、物件、類、函式引數等。

讓我們透過下面的示例來了解它。

示例

在下面的程式碼中,我們有一個 'Wrapper' 介面,它將資料型別作為引數。我們建立了 stringWrapper 和 numberWrapper 變數,並將字串和數字資料型別作為引數傳遞。

// Define a generic interface with a single property
interface Wrapper<T> {
    value: T;
}

// Use the interface with a string type
let stringWrapper: Wrapper<string> = {
    value: "Hello, TypeScript!",
};

// Use the interface with a number type
let numberWrapper: Wrapper<number> = {
    value: 123,
};

console.log(stringWrapper.value); // Output: Hello, TypeScript!
console.log(numberWrapper.value); // Output: 123

編譯後,它將生成以下 JavaScript 程式碼。

// Use the interface with a string type
let stringWrapper = {
    value: "Hello, TypeScript!",
};

// Use the interface with a number type
let numberWrapper = {
    value: 123,
};
console.log(stringWrapper.value); // Output: Hello, TypeScript!
console.log(numberWrapper.value); // Output: 123

輸出

上述示例程式碼將產生以下輸出:

Hello, TypeScript!
123

函式和型別相容性

當我們比較或將一個函式賦值給另一個函式時,TypeScript 編譯器會檢查目標函式是否至少具有與源函式相同的引數和返回型別。如果您將函式 'x' 賦值給函式 'y',則函式 'x' 是目標函式,函式 'y' 是源函式。函式 'y' 中的附加引數不會導致任何錯誤。

示例

在下面的程式碼中,函式 'x' 只包含 1 個引數,而函式 'y' 包含 2 個引數。當我們將函式 'x' 賦值給函式 'y' 時,附加引數 's' 不會導致任何錯誤。

// Defining functions 
let x = (a: number) => { console.log(a); };
let y = (b: number, s: string) => { console.log(b + s); };

y = x; // OK
// x = y; // Error: x does not accept two arguments.

類和型別相容性

當我們比較兩個類時,它只比較例項的成員。類建構函式和靜態成員屬於類本身,因此它們不包含在比較中。

示例

在此程式碼中,變數 'a' 和變數 'b' 具有相同的例項成員。因此,當我們將變數 'a' 賦值給 'b' 時,它不會引發任何錯誤。

class Animal {
  feet: number;
  constructor(name: string, numFeet: number) {}
}

class Size {
  feet: number;
  constructor(meters: number) {}
}

let a: Animal;
let s: Size;
// Works because both classes have the same shape (they have the same instance properties).
a = s;

您可以使用介面和泛型進行型別相容性。對於函式,目標函式應該至少具有與源函式相同的引數。對於類的型別相容性,它們應該具有相同的例項成員。

廣告