TypeScript - 使用筆記 (3)
Type
- 單純想表示靜態格式資料概念時使用
- 不需要擴展新的屬性時使用
type StringOrNum = string | numbertype objWithName = { name: string, uid: StringOrNum }const logDetails = (uid: StringOrNum, item:string) => { console.log(`${item} has a uid of ${uid}`)}const greet = (user: objWithName) => { console.log(`${user.name} says hello`)}
可選的物件屬性,加上?
type Product = { title: string; price?: number; inStock?: boolean;}
定義聯合類型的別名
type Price = number | string;interface Product { title: string; price: Price; // 相同於number | string inStock: boolean;}
限定某個屬性的值
- size屬性的值只能是
S
,M
,L
,XL
type Price = number | string;type Size = "S" | "M" | "L" | "XL";interface Product { title: string; price: Price; inStock: boolean; size: Size}
Interface
- 定義物件的型別
- 一般首字母大寫
- 對「物件的形狀(Shape)」進行描述
- 賦值的時候,變數的形狀必須和介面的形狀保持一致
- 希望資料被重複多方利用時使用
- 更方便進行擴展物件屬性
interface IsPerson { name: string; age: number; speak(msg: string): void; spend(total: number): number;}const me: IsPerson = { name: 'Yong', age: 29, speak(text: string): void { console.log(text); }, spend(amount: number): number { console.log('I spent', amount); return amount; },};const greetPerson = (person: IsPerson) => { console.log('hello', person.name);};greetPerson(me);
- 有時希望不要完全匹配一個形狀,可以用可選屬性 (
?
) - 可選屬性的含義是該屬性可以不存在
interface Person { name: string; age?: number;}let tom: Person = { name: 'Tom'};
- 希望一個介面允許有任意的屬性
- 使用
[propName: string]
定義了任意屬性取string
型別的值
interface Person { name: string; age?: number; [propName: string]: any;}let tom: Person = { name: 'Tom', gender: 'male'};
設置不能改變的預設值
- 在屬性前方加上
readonly
關鍵字 - 一但給預設值之後,就不能改變
interface Config { readonly baseUrl: string; readonly timeout: number;}// 設置預設值const config: Config = { baseUrl: "https://api.example.com", timeout: 3000,};// 以下程式碼會產生編譯錯誤config.baseUrl = "https://api2.example.com"; // Error: Cannot assign to 'baseUrl' because it is a read-only property
對物件屬性進行擴展
- 可以定義多個同名的interface,裡面的屬性會自動合併
interface Product { title: string; price: number; inStock: boolean;}interface Product { count: number;}const myProduct: Product = { title: '毛衣', price: 300, inStock: true, count: 44}
- 使用
extends
可以繼承另一個Interface
interface Shape { color: string;}interface Circle extends Shape { radius: number;}let myCircle: Circle = { color: "red", radius: 10}
- 繼承多個Interface,使用
,
隔開
interface Shape { color: string;}interface PenStroke { penWidth: number;}interface Circle extends Shape, PenStroke { radius: number;}let myCircle: Circle = { color: "red", radius: 10, penWidth: 5.0}
- 多層物件
interface Product { title: string; price: number; inStock: boolean; category: Category;}interface Category { name: string}let product: Product = { title: 'T-shirt', price: 30, inStock: true, category: { name: '上衣' }}
- 與陣列一起使用,規定陣列內每筆item的格式
interface Product { title: string; price: number; inStock: boolean;}let products: Product[]
- 使用
implements
- 代表該Class必須包含Interface所定義的屬性和方法
interface Myinterface{ name: string; sayHello():void;}class MyClass implements Myinterface { name: string; constructor(name: string){ this.name = name } sayHello(){ console.log('大家好') }}// 實現多個Interfaceinterface Movable { move(): void;}interface Sizable { resize(): void;}class Shape implements Movable, Sizable { move() { console.log("Shape moved"); } resize() { console.log("Shape resized"); }}
Type 和 Interface的差別
特性/概念 | Type | Interface |
---|---|---|
用途 | 廣泛,包括原始值、物件、函式等 | 主要用來宣告物件 |
重複宣告 | 不允許 | 允許,會自動合併 |
運算符 | 支持,如 | 和 & | 不支持 |
實現和擴展 | 不可實現或擴展 | 可以被 class 實現或擴展 |
Class
構造函式
example1
class User { nickName: string; age: number; constructor( name: string, age:number){ this.nickName = name this.age = age }; sayHello(){ console.log(this.nickName) }}const user1 = new User('Yong', 18)const user2 = new User('Lewis', 22)//this 會是調用方法的實例對象user2.sayHello() // Lewis
example2
class Invoice { client: string; details: string; amount: number; constructor(c: string, d: string, a: number) { this.client = c; this.details = d; this.amount = a; } format() { return `${this.client} owes $${this.amount} for ${this.details}`; }}const invOne = new Invoice('Noel', 'play guitar', 300);const invTwo = new Invoice('Liam', 'sing a song', 300);// 只用以上格式的物件可以加入陣列let invoices: Invoice[] = [];
繼承
- 如果在子類新增和父類相同的方法,則子類的方法會覆蓋父類方法
class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log("在叫"); }}class Wolf extends Animal { run() { console.log(`${this.name} 在跑~~-`); } sayHello() { console.log("嗷嗷嗷嗷"); }}class Fox extends Animal {}const wolf = new Wolf("小狼", 17);wolf.run();wolf.sayHello();
super
關鍵字- 子類別中使用
super
關鍵字來呼叫父類別的建構函式和方法
class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log("動物在叫"); }}class Fox extends Animal { constructor(name: string, age: number) { // 添加了 age 參數 super(name, age); console.log(this.name); } roar() { super.sayHello(); }}const fox = new Fox("小胡", 3); // 添加了 age 參數console.log(fox.name); // 小胡fox.roar(); // 輸出:動物在叫
abstract
:用於定義抽象類別和其中的抽象方法- 抽象類別不允許被實例化
- 是用來被繼承的class
abstract class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log("動物在叫"); }}let b = new Animal('大嘴鳥', 6); //error: Cannot create an instance of an abstract class.
- 創建抽象方法
abstract class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } // 抽象方法 // 只能定義在抽象實例,子類必須對抽象方法重寫 abstract sayHello():void}class Bird extends Animal {}// error 沒有定義抽象方法
資料封裝
- 屬性在實例中被定義,可以任意修改
- 若屬性可以任意被修改,會導致對象中的資料不安全
class Person { name: string; age: number; constructor(name:string, age: number){ this.name = name this.age = age }}const per = new Person('大壯', 40)// 直接修改per.name = '達伊'per.age = -14 // 可能會被修改成怪怪的值
可以在屬性前,加上修飾符
public
:可以在任意位置訪問/修改 (預設)private
:私有屬性- 只能在class內部進行訪問/修改
class Person { private _name: string; private _age: number; constructor(name:string, age: number){ this._name = name this._age = age }}const per = new Person('大壯', 40)// 無法在class外面被修改per.name = '達伊' //errorper.age = -14 //error
getter & setter
- 想要對類別的內部狀態進行更細緻的控制時,可以在設置或取得屬性值時,執行額外的邏輯,如驗證、轉換或其他副作用
- 隱藏實現細節:使用 getter 和 setter 可以隱藏物件內部狀態的實作細節
class Person { private _name: string; private _age: number; constructor(name:string, age: number){ this._name = name this._age = age } // getter 取得name屬性 getName() { return this._name } // setter 設置name屬性 setName(value: string) { this._name = value } getAge() { return this._age } setAge(age:number) { // 加上條件限制 if(age >= 0) { this._age = age } }}const per = new Person('大壯', 40)console.log(per.getName()) // 大壯per.setName("波普")console.log(per.getName()) // 波普per.setAge(-20) // 不會執行console.log(per.getAge()) // 40
- 等同於下面簡寫
class Person { constructor(private _name: string, private _age: number) {} get name() { return this._name; } set name(value: string) { this._name = value; } get age() { return this._age } set age(age: number) { if(age >= 0) { this._age = age } }}const per = new Person('大壯', 40)per.name = '卡比'per.age = -20 // 不會執行
- protected:受保護的屬性
- 只能在 當前類 和 當前類的子類 中訪問/修改
- 簡寫
class Person { // 直接將屬性定義在構造函式 constructor(public name:string, public age: number){ }}// 等同於class Person { name: string; age: number; constructor(name:string, age: number){ this.name = name this.age = age }}
參考資料
# TypeScript