Typescript (2) 基本型別定義

基本型別定義

typescript 可以在宣告變數時,後面加上 :型別 的方式來定義,型別定義後就不能被賦予其他型別的值

1
2
3
4
5
let age: number = 30;  // 定義型別 number
let firstName: string = 'mario'; // 定義型別 string
let isFictional: boolean; // 定義型別 boolean
let something: null; // 定義型別 null
let anotherSomething: undefined; // 定義型別 undefined

陣列與物件型別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定義型別為陣列,且陣列內型別為 string
const names: string[] = ['Mario', 'Lucy', 'Peach'];
// 定義型別為陣列,且陣列內型別為 number
const ages: number[] = [1, 2, 3];

// 定義型別為物件,且物件內只能有指定的屬性和對應型別
const users: {
firstName: string,
age: number,
id: number
} = {
firstName: 'mario',
age: 30,
id: 1,
}

函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定義函式內的參數與型別,與回傳值的型別
function addTwoNums(a:number, b:number): number {
return a + b;
}

const substractTwoNumber = (a:number, b:number): number => {
return a - b;
}

// 回傳值的型別可以被自動計算,所以可以省略如下
function addAllNumbers(items: number[]) {
const total = items.reduce((sum, num) => sum + num, 0);
return total;
}

// 有些情況應該明確指定回傳型別
// 遞迴函式(Recursive Function)。
// 函式會呼叫自己,這種情況 TypeScript 在某些情境下無法正常推論回傳型別,因此可能產生 any 的問題
function fibonacci(n: number) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

any 型別

如果宣告變數為 any 型別,代表該變數可以是任何型別。

1
2
3
4
5
6
7
8
9
// 宣告型別為 any 或宣告變數但不給型別,都代表 any 型別。
let age: any;
let title;

age = 30;
age = false;

title = 'text';
title = 5;

陣列內的 any 型別

1
let things: any[] = ['hello', 123, true]

函式內的 any 型別

1
2
3
4
5
6
function callName(value: any): any {
return value + value
}

const resultOne = callName('hello')
const resultTwo = callName(5)

tuple 型別

Tuple 是一種「固定長度、固定型別順序」的陣列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let tuple: [string, number];
tuple = ["Tim", 25]; // ✅ 正確
tuple = [25, "Tim"]; // ❌ 順序錯誤
tuple = ["Tim"]; // ❌ 少一個元素

let hsla: [number, string, string, number];
hsla = [200, '100%', '50%', 1];

function useCoords(): [number, number] {
const lat = 100;
const lon = 50;
return [lat, lon];
}
const [lat, lon] = useCoords();
  • named tuple:

我們可以在 tuple 的每個元素加上 名稱(label),讓可讀性更好。

1
let user = [id: number, name: string, isAdmin?: boolean];

這裡的 id、name、isAdmin 就是 tuple 的具名元素,第一個值代表 id,第二個代表名稱,帶三個代表是否為會員。

Interface

在 TypeScript 裡,interface(介面)是一種用來定義物件結構的型別。它主要用於描述 物件有哪些屬性、屬性的型別、方法簽名。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Post {
title: string,
body: string,
tags: string[],
created_at: Date,
}

const newPost: Post = {
title: 'my first post',
body: 'something',
tags: ['game', 'life'],
created_at: new Date(),
}

函式中使用

1
2
3
4
5
6
7
8
9
10
11
function createPost(post: Post):void {
console.log(`Created post ${post.title} by ${post.author.name}`)
}

createPost({
title: 'my post',
body: 'my body',
tags: ['life'],
created_at: new Date(),
author: authorOne
});

陣列中使用

1
2
3
4
5
6
7
8
9
let posts: Post[] = [];

posts.push({
title: 'my first post',
body: 'something',
tags: ['game', 'life'],
created_at: new Date(),
author: authorOne
})

Union Types

在 TypeScript 中,Union Types 允許一個變數可以是 多種型別 之一。
用 |(直線符號)來連接多個型別。
這在實務上很常用,因為有時候資料來源不只一種型別。

1
2
3
4
5
6
7
8
9
let value: string | number;

value = "Hello"; // ✅ 合法
value = 123; // ✅ 合法
value = true; // ❌ 錯誤,因為沒包含 boolean

type UserRole = 'guest' | 'member' | 'admin';
let userRole: UserRole = 'guest';
// userRole 僅能是 'guest'、'member'、'admin' 其中之一

函式參數中的 Union Types

1
2
3
4
5
6
function printId(id: number | string) {
console.log("Your ID is: " + id);
}

printId(101); // ✅ number
printId("abc"); // ✅ string

如果函式參數使用 Union Types,. 只能使用所有型別「共有」的方法或屬性

1
2
3
4
5
6
7
function printLength(value: string | string[]) {
console.log(value.length); // ✅ OK
}

function toUpperCase(value: string | number) {
console.log(value.toUpperCase()); // ❌ Error: number 沒有 toUpperCase()
}

解法是先判斷型別

1
2
3
4
5
6
7
function swapId(id: number | string) {
if (typeof id === 'string') {
return parseInt(id);
} else {
return id.toString();
}
}

與 Array 搭配

1
2
3
4
5
let data: (string | number)[] = [1, "apple", 2, "banana"];

data.push(3); // ✅
data.push("cherry"); // ✅
data.push(true); // ❌

Partial type

Partial 是 TypeScript 很常用的工具型別,意思一句話就夠:
把一個型別的「所有屬性變成可選(optional)」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type User = {
id: number,
username: string,
role: UserRole,
}
type UpdateUser = Partial<User>;

const updateUser = (id: any, update: UpdateUser) => {
const user = users.find(u => u.id === id);
if (!user) {
console.error('User not found');
return;
}
Object.assign(user, update);
}

// 不用每次傳整個 User
updateUser(2, { role: 'admin' });
updateUser(3, { username: 'charlie_updated' });

Omit type

Omit<T, K> 是 TypeScript 超常用的工具型別,從型別 T 中「移除指定的屬性 K」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type User = {
id: number
name: string
email: string
}

type UserWithoutId = Omit<User, 'id'>

const addNewUser = (newUser: UserWithoutId): User => {
const user = { id: nextUserId++, ...newUser };
users.push(user);
return user;
}
// 可以不傳入 id
addNewUser({ username: 'frank', role: 'member' });

Generics 泛型

代表 泛型型別參數(Type Parameter)
T 不是固定型別(不是 string / number)
T 會在「呼叫函式時」由 TypeScript 自動推斷

1
2
3
function getLastItem<T>(array: T[]): T {
return array[array.length - 1];
}

表示: array 是一個陣列,陣列裡的每個元素型別都是 T,這個函式 一定回傳一個 T。

1
2
3
T = number  →  number[]
T = string → string[]
T = User → User[]

泛型不只可以用在 Function

例如:

1
2
3
4
type ApiResponse<T> = {
data: T
isError: boolean
}

這裡的 T 代表:API 回傳的 data 到底是什麼型別?
假設 API 回傳 User:

1
2
3
4
5
6
7
8
9
10
11
12
type User = {
name: string
age: number
}
type UserResponse = ApiResponse<User>
const response: UserResponse = {
data: {
name: "Tim",
age: 20
},
isError: false
}

dom 元素 TYPE

1
2
const list = document.querySelector<HTMLUListElement>('#list');
const input = document.querySelector<HTMLInputElement>('#new-task-title');
  • querySelector() 可以選擇任意 CSS 選擇器。
  • 若找不到元素,回傳 null。
  • TypeScript 會自動推斷型別為 T | null。
1
const form = document.getElementById('#new-task-form') as HTMLFormElement | null;

getElementById 回傳 HTMLElement | null,所以要用斷言轉型