TS - Basic TypeScript
TypeScript 起手式
- 安裝 TypeScript 編譯器。
npm install -g typescript
- 建立一個 ts 檔案,如:hello.ts。
- 輸入:
function greet(person: string){
console.log(`Hello, ${person}`)
}
greet('world')
- 打開終端機輸入:
tsc hello.ts
。 - 執行編譯過後出現 JavaScript 檔案。
型別註記
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 的基礎型別:boolean
、number
、string
、null
、undefined
、symbol
,通通可以用像下面這個例子這麼簡單的方式做型別註記:
let isDone: boolean = false
TypeScript 提供一個型別叫做 any
,代表什麼型別都可以,請盡量不要使用,不然就喪失 TypeScript 的使用意義了。
型別推論
let myName = 'Jeremy'
如果在第一次宣告變數時有給予值,其實 TypeScript 就隱性地為這個變數加上一個型別了。
聯合型別
聯合型別就是讓我們的變數可以儲存多種型別中的一種:
let num: number | string
num = 9
num = 'nine'
TypeScript 只能存取聯合型別中各型別共有的方法。
const getLength = (x: string | number):number =>{
return x.length
}
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) 這個模板來做型別的註記。
基本用法
- 先建個叫 Item 的介面:
interface Item {
name: string,
price: number
}
- 把這個介面作為型別註記在新的物件下:
let phone: Item = {
name: 'IPhone',
price: 34000
}
這樣做的限制就是物件變數必須長得跟介面一致,不能少放一個屬性,也不能多放一個屬性。
可選屬性
用來解決不能少放屬性的問題。比如今天某個屬性可有可無,比如價格,可以在介面以可選屬性來為它做定義:
interface Item {
name: string,
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]
介面也可以來做陣列的型別註記,但比較麻煩
函式的型別
const sum = (x: number, y: number): number =>{
return x + y
}
可選參數
上述這樣寫就必須嚴格遵守 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 陣列。