TypeScript - 類型相容性

你好,未來的編程法師們!今天,我們將踏上一段令人興奮的旅程,探索 TypeScript 的世界,並探究類型相容性這個引人入勝的概念。別擔心如果你是編程新手——我會成為你的友好導遊,我們將一步一步地攻克這個主題。所以,拿起你的虛擬魔杖(鍵盤),讓我們來施展一些 TypeScript 的魔法!

TypeScript - Type Compatibility

TypeScript 如何進行類型相容性檢查?

想像一下你試圖將不同形狀的積木塞進一個洞裡。TypeScript 的類型相容性有點像這樣——它關乎於一種類型是否能夠適合另一種類型。但我們處理的是數據類型,而不是實體形狀。

結構類型

TypeScript 使用我們所謂的“結構類型”。這意味著它更關心對象的形狀,而不是它的確切類型名稱。讓我們看一個例子:

interface Pet {
name: string;
}

class Dog {
name: string;
}

let pet: Pet;
let dog = new Dog();

pet = dog; // 這是沒問題的!

在這個神奇的動物園中,TypeScript 說:“嘿,PetDog 都有一個類型為 string 的 name 屬性。在我看來它們一樣,所以它們是相容的!”這就像說一個方形的楔子能夠塞進一個方形的洞,即使一個被稱為“方形”而另一個被稱為“塊”。

鴨子類型

編程中有個有趣的短語:“如果它走起來像鴨子,叫起來像鴨子,那麼它一定就是鴨子。”這就是鴨子類型的精髓,而 TypeScript 也擁抱這種哲學。讓我們看看它是如何工作的:

interface Quacker {
quack(): void;
}

class Duck {
quack() {
console.log("Quack!");
}
}

class Person {
quack() {
console.log("我正在模擬鴨子的叫聲!");
}
}

let quacker: Quacker = new Duck(); // 明顯是沒問題的
quacker = new Person(); // 這也是可以的!

TypeScript 不在乎 Person 沒有被明確聲明為 Quacker。它只關心 Person 有一个 quack 方法,就像 Quacker 一樣。所以 DuckPerson 都與 Quacker 相容。

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

有效地使用類型相容性就像是一個熟練的拼圖解決者。以下是一些提示:

1. 理解對象字面量的檢查

TypeScript 對對象字面量更加嚴格。讓我們看看為什麼:

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

let p: Point;

// 這是沒問題的
p = { x: 10, y: 20 };

// 這會導致錯誤
p = { x: 10, y: 20, z: 30 };

TypeScript 說:“等一下!我要求的是 Point,但你給了我多餘的東西(z)。這是不允許的!”這有助於發現可能不正確使用對象的潛在錯誤。

2. 使用可選屬性

有時候,你希望更加靈活。這就是可選屬性派上用場的地方:

interface Options {
color?: string;
width?: number;
}

function configure(options: Options) {
// ...
}

configure({ color: "red" }); // 沒問題
configure({ width: 100 }); // 沒問題
configure({}); // 同樣沒問題!

通過使屬性成為可選的(使用 ?),你告訴 TypeScript:“這些屬性不一定要存在。”

函數和類型相容性

函數就像是編程中的瑞士軍刀——它們非常多用途。讓我們看看類型相容性是如何與它們工作的:

參數相容性

TypeScript 對函數參數非常寬容:

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

y = x; // 沒問題
x = y; // 錯誤

這可能看起來有些反直覺,但它是安全的。TypeScript 說:“如果你期望一個接受兩個參數的函數,那麼給它一個接受較少參數的函數是可以的。多餘的參數將被忽視。”

返回類型相容性

返回類型也需要相容:

let x = () => ({name: "Alice"});
let y = () => ({name: "Alice", location: "奇異國度"});

x = y; // 沒問題
y = x; // 錯誤

返回比預期多的東西是可以的,但返回少的則不行。這就像訂購一個帶有配料的小麵包,然後獲得免費的額外配料——那很好!但如果訂購了帶配料的小麵包卻只得到麵包皮,你會感到失望。

類和類型相容性

類就像是對象的藍圖,它們遵循相似的相容性規則:

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

class Size {
feet: number;
}

let a: Animal;
let s: Size;

a = s; // 沒問題
s = a; // 沒問題

TypeScript 只關心實例成員。它說:“兩者都有 feet 屬性?對我來說這就足夠了!”

私有和受保護的成員

然而,當類有私有或受保護的成員時,事情會變得更加嚴格:

class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
constructor() { super("Rhino"); }
}

class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino; // 沒問題
animal = employee; // 錯誤:'Animal' 和 'Employee' 不相容

即使 AnimalEmployee 看起來一樣,TypeScript 也將它們視為不同的,因為它們的 private 成員來自不同的聲明。

結論

這就是 TypeScript 的類型相容性,我的編程學徒們!我們一起穿越了 TypeScript 的類型相容性之地。記住,TypeScript 在這裡是為了幫助你寫出更好、更安全的代碼。它就像一個友好的巫師在你看著你的肩膀,當你即將犯錯時輕輕推你一把。

持續練習,持續嘗試,很快你就能像專家一樣施展 TypeScript 的魔法!下次見,快樂編程!

方法 描述
結構類型 根據類型的結構而不是它們的名稱檢查相容性
鴨子類型 如果它有所需的屬性/方法,就被認為是相容的
對象字面量檢查 對象字面量更加嚴格,以捕獲潛在錯誤
可選屬性 使用 ? 使屬性可選,增加靈活性
參數相容性 具有較少參數的函數可以分配給具有更多參數的函數
返回類型相容性 函數可以返回比預期更多的東西,但不能少於預期
類相容性 基於實例成員,而不是構造函數或靜態成員
私有/受保護的成員 有私有/受保護成員的類對其子類以外的類不兼容

Credits: Image by storyset