TypeScript - 類型相容性
你好,未來的編程法師們!今天,我們將踏上一段令人興奮的旅程,探索 TypeScript 的世界,並探究類型相容性這個引人入勝的概念。別擔心如果你是編程新手——我會成為你的友好導遊,我們將一步一步地攻克這個主題。所以,拿起你的虛擬魔杖(鍵盤),讓我們來施展一些 TypeScript 的魔法!
TypeScript 如何進行類型相容性檢查?
想像一下你試圖將不同形狀的積木塞進一個洞裡。TypeScript 的類型相容性有點像這樣——它關乎於一種類型是否能夠適合另一種類型。但我們處理的是數據類型,而不是實體形狀。
結構類型
TypeScript 使用我們所謂的“結構類型”。這意味著它更關心對象的形狀,而不是它的確切類型名稱。讓我們看一個例子:
interface Pet {
name: string;
}
class Dog {
name: string;
}
let pet: Pet;
let dog = new Dog();
pet = dog; // 這是沒問題的!
在這個神奇的動物園中,TypeScript 說:“嘿,Pet
和 Dog
都有一個類型為 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
一樣。所以 Duck
和 Person
都與 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' 不相容
即使 Animal
和 Employee
看起來一樣,TypeScript 也將它們視為不同的,因為它們的 private
成員來自不同的聲明。
結論
這就是 TypeScript 的類型相容性,我的編程學徒們!我們一起穿越了 TypeScript 的類型相容性之地。記住,TypeScript 在這裡是為了幫助你寫出更好、更安全的代碼。它就像一個友好的巫師在你看著你的肩膀,當你即將犯錯時輕輕推你一把。
持續練習,持續嘗試,很快你就能像專家一樣施展 TypeScript 的魔法!下次見,快樂編程!
方法 | 描述 |
---|---|
結構類型 | 根據類型的結構而不是它們的名稱檢查相容性 |
鴨子類型 | 如果它有所需的屬性/方法,就被認為是相容的 |
對象字面量檢查 | 對象字面量更加嚴格,以捕獲潛在錯誤 |
可選屬性 | 使用 ? 使屬性可選,增加靈活性 |
參數相容性 | 具有較少參數的函數可以分配給具有更多參數的函數 |
返回類型相容性 | 函數可以返回比預期更多的東西,但不能少於預期 |
類相容性 | 基於實例成員,而不是構造函數或靜態成員 |
私有/受保護的成員 | 有私有/受保護成員的類對其子類以外的類不兼容 |
Credits: Image by storyset