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的差別

特性/概念TypeInterface
用途廣泛,包括原始值、物件、函式等主要用來宣告物件
重複宣告不允許允許,會自動合併
運算符支持,如 |&不支持
實現和擴展不可實現或擴展可以被 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