TS 筆記 | TypeScript 基礎

閱讀時間約 13 分鐘

TypeScript 起手式

  1. 安裝 TypeScript 編譯器。
npm install -g typescript
  1. 建立一個 ts 檔案,如:hello.ts。
  2. 輸入:
function greet(person: string){
console.log(`Hello, ${person}`)
}

greet('world')
  1. 打開終端機輸入:tsc hello.ts
  2. 執行編譯過後出現 JavaScript 檔案。

現在,歡迎來到 TypeScript 的世界。



型別註記

TypeScript, TypeScript…顧名思義型別就是 TypeScript 的重點,它的出現就是為了解決 JavaScript 型別組織鬆散的問題。

所以 TypeScript 的核心就是讓我們手動為我們的變數們加上型別,然後由 TypeScript 為我們做型別的把關,避免因為型別而出現一些非預期的錯誤。

比方說下面這個很經典的算術例子:

const sum = (x, y) =>{
return x + y
}

console.log(sum(1, '1'))

想像一下,預期只是數字相加,但在程式碼某處突然傳入一個字串,就會產生這種 1 + '1' = 11 的錯誤了,這是 JavaScript 型別轉換的鍋。

但如果改成 TypeScript 呢?

const sum = (x: number, y: number): number =>{
return x + y
}

console.log(sum(1, '1'))

在編譯時 TypeScript 就會跳出來說:Argument of type 'string' is not assignable to parameter of type 'number'.

上述的例子就是為參數和函式的 return 做了型別註記,TypeScript 就會依照註記去做檢查。

對於 JavaScript 的基礎型別:booleannumberstringnullundefinedsymbol,通通可以用像下面這個例子這麼簡單的方式做型別註記:

let isDone: boolean = false

特別注意,TypeScript 提供一個型別叫做 any,代表什麼型別都可以,請盡量不要使用,不然就喪失 TypeScript 的使用意義了。



型別推論

let myName = 'Jeremy'

上面這段例子不會被報錯,會順利編譯出來。Why ~? 不是說 TypeScript 會需要我們做型別註記嗎?啊,難道是它變成剛剛提到的型別 any 了!

錯了!絕對不是 any,如果往下編譯這段 myName = 123,就會看到 TypeScript 噴這段錯誤給你了:Type 'number' is not assignable to type 'string'.

從這個錯誤我們也可以得知,myName 已經被型別註記為 string 了。Why ~~~?

這就是 TypeScript 的型別推論,如果在第一次宣告變數時我們有給予值,其實就隱性地為這個變數加上一個型別了。

也因為型別推論,TypeScript 也建議不用在上述的例子後面自己手動做型別註記,聰明的 TypeScript 會為我們處理好的。



聯合型別

聯合型別就是讓我們的變數可以儲存多種型別中的一種,比如我的 num 變數可以儲存阿拉伯數字或英文:

let num: number | string 
num = 9
num = 'nine'

一切正常運作,真是太好了!所以聯合型別就這樣過了吧…才怪,有一個要注意的點:

TypeScript 只能存取聯合型別中各型別共有的方法。

啥意思?看一下範例:

const getLength = (x: string | number):number =>{
return x.length
}

Oops,TypeScript 丟了一個錯誤給我們:Property 'length' does not exist on type 'string | number'.

那是因為 length 不是 number prototype 下的方法,但如果是像 toString 這種兩者都有的方法就可以編譯成功囉!



型別斷言

這個神奇的名字在做什麼事情?

答案是用來手動指定一個值的型別。聽起來很抽象吧,舉個例來說,用在前面的聯合型別上。

const getLength = (x: string | number):number =>{
if(x.length){
return x.length
}else{
return x.toString().length
}
}

依照上面聯合型別知道的,這東西一定會噴錯,但如果加上型別斷言就不一樣囉:

const getLength = (x: string | number):number =>{
if((x as string).length){
return (x as string).length
}else{
return x.toString().length
}
}

白話一點就是為參數指定在什麼型別下要幹什麼事。



物件的型別註記

介面

跟基本型別的註記不一樣,物件的型別註記會透過先建立介面 (interfaces) 這個模板來做型別的註記。(在這裡先不要管介面在 OOP 中在幹嘛,我不太懂 OOP,先會用就好 ><)

舉例來說,可以先建個叫 Item 的介面:

interface Item {
name: string,
price: number
}

然後把這個介面作為型別註記在新的物件下:

let phone: Item = {
name: 'IPhone',
price: 34000
}

這樣做的限制就是,我的物件變數必須長得跟介面一致,也就是我不能少放一個屬性,也不能多放一個屬性。

可選屬性

如果今天某個屬性可有可無,比如價格,可以在介面以可選屬性來為它做定義:

price?: number

這樣就解決不能少放屬性的問題了。

任意屬性

那我想多放屬性呢?這時就要用任意屬性了。

interface Item {
name: string,
price?: number,
// 添加一個任意屬性
[propName: string]: any
}

這樣就能新增一個原先不存在的屬性了:

let phone: Item = {
name: 'IPhone',
date: '2023-11-13'
}

唯讀屬性

在屬性不想被更動時,可以在前面加上 readonly 讓它變成唯讀:

interface Item {
readonly name: string,
price?: number,
[propName: string]: any
}



陣列的型別

雖然可以用介面來做型別註記,但大可不必這麼麻煩!

陣列的型別只要使用 “型別 + []” 就可以了,像這樣:

const arr: number[] = [1, 2, 3]

這樣寫就是明確聲明這是一個存數字的陣列。



函式的型別

JavaScript 作為 FP 的經典語言,函式是這個國度的一等公民。

最一開始的時候寫過一個例子:

const sum = (x: number, y: number): number =>{
return x + y
}

這就是一個典型的函式型別:定義參數接收型別、定義 return 回傳值型別。

可選參數

但同樣的,像上述這樣寫我們就必須嚴格遵守 TypeScript 中的規範,定義多少參數我就該給多少參數,一個不多、一個不少,但同樣我們也可以把參數定義為可選參數來決定是否可以不要傳入這個參數:

const sum = (x: number, y?: number):number =>{
return (x + y)
}

這裡定義 y 為可選參數而不是 x 是有小規範的,那是因為可選參數後面不允許出現必須參數了,所以請記得可選參數永遠要寫在後面。

我們也可以為參數代入預設值:

const sum = (x: number, y: number = 1):number =>{
return (x + y)
}

這時 y 會被自動當作是可選參數,但它不受 “可選參數後面不允許出現必須參數” 這條規則規範了。

剩餘參數

函式的參數還有一個很個特殊的剩餘參數用法,先看範例:

function concatString(separator: string, ...strings: string[]): string {
return strings.join(separator);
}

const result = concatString(", ", "apple", "banana", "orange")
console.log(result) // apple, banana, orange

我們用 ... 表示接收數量不定的參數,並存入到一組陣列中,以上面的範例就是把 apple、banana、orange 一一存入 strings 陣列。

過載

過載,或稱多載,隨便啦,反正英文都叫 overloading,是一個 JavaScript 沒有的東西,但 TypeScript 提供來提高 code 維護性與可讀性的機制。

過載可以讓一個函式接受不同數量或型別的參數時去做不一樣的事。Well...怎麼聽起來這麼像聯合型別 + 型別斷言,而且自己寫了幾個例子發現還真的可以這樣幹,於是就疑惑地把問題拋給 chatGPT 了,他是這樣回答我的:

當你的函數有多種輸入和輸出情境,或者需要提供多種配置選項時,使用函數過載可以使你的程式碼更具可讀性和可維護性。

Hmm...這解釋多少有解答我的疑惑,但還是有一種尚未完全撥雲見日的感覺,直到我看到有人這樣解釋:

儘管你能用型別聯集為函式的參數與回傳值定義一系列可用的型別,卻不見得能精確表達出他們彼此之間的關係......多載可以更清楚描述函式的各個型別彼此之間的關係。

作者給了一段範例:

function calculateTax(amount: number | null): number | null {
if (amount !== null) {
return amount * 1.2;
} else {
return null;
}
}

let taxAmount: number | null = calculateTax(100)
if(typeof taxAmount === 'number'){
console.log('Tax value:', taxAmount)
}

calculateTax 是一個接受聯合型別的函式,寫函式的人告訴它:可以接受 numbernull 的型別資料,也可以回傳型別為 numbernull 的值。但使用者並未明確告訴它當參數是 number 時就該回傳 number、是 null 時就該回傳 null

然後用函式的人使用了這個函式,但他並不確定他傳入什麼參數會得到什麼型別的值,所以他還需要寫段 if function 來做型別保護。

所以上面這兩段文字和 code 說了什麼故事?我們可以得到下列資訊:

  1. 寫出函式的人,以及使用函式的人。
  2. 參數與回傳值的型別關係未明確定義。
  3. 兩人有資訊差,導致用函式的人覺得多寫一個型別保護函是比較安全保險。

所以寫函式的那位為了讓用函式的人放心,使用過載來明確定義參數與回傳值型別之間的關係

// 函數的定義
function calculateTax(amount: number): number;
function calculateTax(amount: null): null;

// 函數的實現
function calculateTax(amount: number | null): number | null {
if (amount !== null) {
return amount * 1.2;
} else {
return null;
}
}

// 函式的調用
let taxAmount: number | null = calculateTax(100)
console.log('Tax value:', taxAmount)

這樣在函式的定義就明確訂出參數與回傳值的型別關係,當調用 calculateTax 時,TypeScript 編譯器就會依序查找我們的定義直至匹配的那一個。



參考資料

  1. TypeScript 新手指南
  2. TypeScript 邁向專家之路 / Adam Freeman 著 / 許文達 譯 / 旗標出版


18會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
創作者要怎麼好好休息 + 避免工作過量?《黑貓創作報#4》午安,最近累不累? 這篇不是虛假的關心。而是《黑貓創作報》發行以來可能最重要的一篇。 是的,我們這篇講怎麼補充能量,也就是怎麼休息。
Thumbnail
avatar
黑貓老師
2024-06-29
[TS LeetCode] 122. Best Time to Buy and Sell Stock II+貪婪演算法 題目摘要 給定一個整數陣列 `prices`,其中 `prices[i]` 代表第 `i` 天的股票價格。在每一天,你可以決定買入和/或賣出股票。然而,你同一時間只能擁有至多一股股票。你可以在同一天內買入然後立刻賣出股票。找出並返回你可以實現的最大利潤。
Thumbnail
avatar
毛怪
2023-10-19
[TS LeetCode] 121. Best Time to Buy and Sell Stock題目摘要 給定一個陣列 prices,其中 prices[i] 代表第 i 天的股票價格。你希望透過在某一天購買一股股票,並在未來的某一天賣出它,以最大化你的利潤。如果無法獲得任何利潤,則返回 0。
Thumbnail
avatar
毛怪
2023-10-19
[TS LeetCode] 169. Majority Element .Boyer-Moore 摩爾投票演算法 題目摘要: 在這篇文章中,我們將討論如何使用摩爾投票算法找出一個陣列中的「主要元素」。主要元素指的是在陣列中出現次數超過一半的元素,並且我們可以確定它一定存在於陣列中。這個算法的核心思想和應用將在本文中被詳細介紹。 題目知識點: 主要元素的定義和重要性。 摩爾投票算法的工作原理和優點。 先備知識
Thumbnail
avatar
毛怪
2023-10-16
[TS LeetCode] 2631. Group By 重要知識點: 1. TypeScript 全局擴展,使所有陣列都能使用 groupBy 方法。 2. 利用泛型創建彈性函數,提高代碼可重用性。 3. 迭代陣列中的元素,實現遍歷和處理功能。 4. 物件的鍵值對操作,用於建立以函數輸出為鍵的物件。
Thumbnail
avatar
毛怪
2023-10-12
[TS LeetCode 筆記] 2637. Promise Time Limit這篇文章介紹了如何建立一個時間限制的異步函數,以確保操作在指定時間內完成。 - 知識點包括異步編程、Promise使用、計時器函數和函數引數處理,以及錯誤處理。 - 應用情境包括網頁請求超時控制、前端性能優化和遊戲開發。 - 提高應用程式的可靠性和用戶體驗,確保操作不會花費過長的時間。 - 文章內
Thumbnail
avatar
毛怪
2023-10-11
【通訊溝通技巧】揭露的六大電話禮儀 Dos & Don'ts隨著科技的進步和遠程工作的興起,通訊方式正在經歷劇變。然而,這也讓許多人對於傳統的電話禮儀感到困惑。美國禮儀專家Lizzie Post針對這一問題,提出了六大電話禮儀建議,幫助我們在新的通訊環境下保持專業和禮貌。
Thumbnail
avatar
Digiworld
2023-10-11
[ TS LeetCode 筆記 ] 2715. Timeout Cancellation. [Leetcode 筆記] TypeScript 2715. Timeout Cancellation 了解和有效使用 clearTimeout 和 setTimeout 可以提高JavaScript程序的效率和響應性。
Thumbnail
avatar
毛怪
2023-10-10
眼部按摩器tokuyo TS-183使用一年心得上週的雙11大家有買東西嗎? 去年雙11購入了tokuto的眼部按摩器TS-183使用滿一年心得分享給大家,希望能幫助跟我一樣眼睛容易酸澀又找不到相關心得文的人~
Thumbnail
avatar
Cheryl
2022-11-15
遇見的Covid與錯過的T•S Eliot關於我錯過的佛地魔與艾略特
Thumbnail
avatar
Fulya
2022-01-16
攻略影評寫作的三個關鍵:關於評論寫作的do's and don'ts嗨大家好,在經營IG的一年多以來,最多人會問的就是有關於影評寫作的問題。在這裡會給大家一些提升你寫作技巧的方法,讓你更精準的能夠表達你的論述。
Thumbnail
avatar
黃彥瑄
2020-08-26