Published on

[學習筆記] 初入 TypeScript 世界 (2):型別 Type

Article Directory

  • TypeScript 的型別
    • 空值 Void:沒有回傳值
    • 任意型別 any:不檢查型別
    • Never:不會有回傳值
    • 陣列型別 Array
    • 列舉型別 enum
    • 未知型別 unknown

TypeScript 和 JavaScript 最明顯可見的差異就是型別,JavaScript 是弱型別語言,TypeScript 是原生 JavaScript 的延伸,在編譯後會生成 *.js 的程式碼供使用,TypeScript 的誕生主要也是要來解決 JavaScript 型別的問題。

TypeScript is a programming language that adds types to JavaScript.

TypeScript 的型別

大家熟悉的 JavaScript 資料型別有兩種,原始資料型別(boolean, number, string, null, undefined, Symbol)和物件型別(Object, Array, Function)。

之前的文章可以複習:
- [學習筆記] JavaScript 的複雜資料類型(1):物件 Object
- [學習筆記] JavaScript 的複雜資料類型(2):陣列 Array 及陣列方法 Array Method
- [學習筆記] JS ES6 中的新資料型別:Symbol

在 TypeScript 中,除了上面這些既有的,還增加了空值(Void)、任意型別(any)和 Never 這三個特殊型別

- 空值 Void:沒有回傳值

只能賦值為 null 或 undefined

- 任意型別 any:不檢查型別

類似回歸 JS 的弱型別,程式不管變數的資料型別也不管之後有沒有被賦值,但是濫用 any 的話等於失去使用 TypeScript 開發的意義了!

any 型別的變數,可以操作任意型別的屬性或方法

- Never:不會有回傳值

代表函式進入無窮迴圈或被中斷執行,永遠不會有回傳值的狀況,常用於函式的除錯

- 陣列型別 Array

陣列又分為以下四種

1. 型別+方括號 type[]:表示資料為陣列,以及裡面的值的型別

const years: number[] = [2020, 2021, 2022, 2023]

宣告後,陣列中的項就不允許出現其他型別

const years: number[] = [2020, 2021, '2022', 2023]

// Type 'string' is not assignable to type 'number'

常使用 any 來表示陣列中允許出現任意型別

const arr: any[] = [2, 'Amy', { email: 'amy@gmail.com' }]

2. 陣列泛型 Array Generic:Array<elemType>

const years: Array<number> = [2020, 2021, 2022, 2023]

3. 用介面 Interface 表示陣列

這個之後筆記介面 Interface 的時候會詳細說明

4. 類別陣列 Array-like Object

- 列舉型別 enum

使用上與 JS 的物件類似,但可將變數的範圍限制在某些限制下進行存取並賦予定義,可以讓程式碼更好維護,也增加可讀性

// 宣告一個名為 Week 的 Enum type 變數
enum Week {
  Sunday,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
}

const today: Week = Week.Friday
console.log(today === Week.Friday) // true

console.log(Week[5]) // Friday
console.log(Week['Friday']) // 5
//將Week內的變數指向字串
enum Week {
  Sunday = 'Sun',
  Monday = 'Mon',
  Tuesday = 'Tue',
  Wednesday = 'Wed',
  Thursday = 'Thu',
  Friday = 'Fri',
  Saturday = 'Sat',
}
console.log(Week[5]) // Friday
console.log(Week['Fri']) // undefined
console.log(Week['Friday']) // Fri
console.log(Week.Friday) // Fri

參考資料:https://medium.com/@notwist123/typescript-列舉型別-enumerate-96fc2eedd581

- 未知型別 unknown

這是 TS3.0 新增的型別,可以說是 any 型別的安全版本

1. 可以接受任何型別賦值(和 any 一樣)

unknown 型別的變數可以接受被賦予任何型別的值

let value: unknown

value = true
value = 10
value = 'Hello'
value = []
value = {}
value = null
value = undefined
value = new TypeError()
value = Symbol('type')

2. unknown 型別只能賦值給 anyunknown 自己

unknown 型別的變數只能被賦予給型別為 anyunknown 的變數,若賦值給其他型別的變數都會報錯

unknown 代表對值一無所知,因此無法賦值給明確的型別

let value: unknown

let value1: unknown = value
let value2: any = value
let value3: boolean = value // Error
let value4: number = value // Error
let value5: string = value // Error
let value6: object = value //Error
let value7: any[] = value //Error
let value8: void - value // Error

3. 變數被註記為 unknown 型別時,所有的屬性或方法都會報錯

相較於 any 型別,限制了屬性及方法的操作,讓 unknown 型別不如 any 型別的行為那樣難以控制

會報錯的特性,也提升了程式碼的維護性

4. unknown 型別要進行限縮才能使用屬性或方法

  • 型別檢測 type guard
// 宣告一型別為 unknown 的變數
let isUnknown: unknown

// Error: Type 'unknown' is not assignable to type 'number'
let value: number = isUnknown

// 限縮變數的型別後,就可以賦值 unknown 型別值給變數
if (typeof isUnknown === 'number') {
  value = isUnknown
}
  • 型別斷言 Type Assertion

先將 unknown 型別的參數斷言為別的型別,即可以使用該斷言型別的屬性及方法

const value: unknown = 'Hello'
const someStr: string = value as string
const otherStr = someStr.toUpperCase() // HELLO

5. 在聯合型別中出現的話,可以簡化

若在聯合型別中含有 unknown 型別,如: unknown | string 其實可以直接簡化為unknown,因為有了 unknown,該變數就可以接受任何型別的值,string 型別的定義就沒有意義

◆ 字面值型別 Literal Types

用來限定變數只能使用列舉的值

type brand: 'iphone' | 'android' | 'samsung'

◆ 型別別名 Type Aliases

型別別名是用來給型別取一個新名字,常用在聯合型別

如果這個聯合型別會一直重複出現在程式碼中,就可以幫他取一個別名,作為之後方便使用在定義型別時的代稱

type StringOrNum = string | number

// 下面兩個相等
type objWithName = { name: string; id: StringOrNum }
type objWithName2 = { name: string; ud: string | number }

//----------------------

type Book = {
  name: string
  price: number
}

const book1: Book = {
  name: 'Learn Typescript',
  price: 250,
}

◆ 泛型 Generics

指在定義函式、介面或類別時,不預先指定型別,而是在使用的時候在指定型別的特性,形式通常為 <type>

前面介紹陣列型別時,也有提到陣列泛型的定義方式:

const arr: number[] = [1, 2, 3]
const arr2: Array<number> = [1, 2, 3]
const printArr: number[] = (arr: number[]) => {
  console.log(arr)
}

const printArr: Array<number> = (arr: Array<number>) => {
  console.log(arr)
}

上面定義兩個只接收數字的陣列,但如果想要在使用的時候,再決定陣列是字串還是數字的話,那就可以用泛型來處理。

在函式後面加上 <Type> 表示動態型別,<Type> 的命名是可以自己定義的,常見的是 <T><Type>

然後將參數 arr 指定為此型別,函式也回傳 Type[],這樣就能依據傳入陣列的型別去定義回傳的型別資料。

const printArr<Type>: Type[] = (arr: Type[]) => {
	console.log(arr)
}

React 中的 useState 最好是用泛型的方法定義型別

如果 state 單純(非物件),也可以用給預設值的方式定義型別,如果是複雜的物件,就可以搭配泛型

// useState 的預設值留空
const [content, setContent] = useState<string>()

type Device = {
  brand: string
  price: number
}
const [device, setDevice] = useState<Device>()

// 給預設值,讓 TS 推論型別
const [content, setContent] = useState('')

打一打不小心有點多,今天就先到這裡啦~

參考資料: