TypeScript - 使用筆記 (3)

Type

  • 單純想表示靜態格式資料概念時使用
  • 不需要擴展新的屬性時使用
type StringOrNum = string | number
type 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('大家好')
    }
}

// 實現多個Interface
interface 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 = '達伊' //error
per.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