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的差別
特性/概念 | 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 = '達伊' //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