iT邦幫忙

2024 iThome 鐵人賽

DAY 13
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 13

TypeScript 初學者也能看的學習指南 13 - Literal Types 明文型別/字面量(值)型別

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240915/201493628gLqtsQzyf.png
本篇要來介紹「Literal Types」以及「Template Literal Types」的概念
並附上範例參考

Literal Types

Literal (adj) 字面的、根本的

Literal Types 中文翻譯為「明文型別」、「字面量(值)型別」,主要是用來限定某些變數或參數的值必須是字面上的值,當今天想賦予字面值以外的型別就會出錯
用一句話來總結:

It is what it looks 它就是它看起來的樣子 👀

let, const 宣告同值,推斷出的結果卻不完全相同

來直接看範例吧!

  • 用 let 和 const 對同個值做宣告(後方的註解:代表推斷出的型別)
let a1 = 'May'; // : string
let b1 = 1; // : number
let c1 = null; // : null
let d1 = undefined; // : undefined
let e1 = true; // : boolean
let f1 = Symbol('test'); // : symbol
let g1 = { name: 'banana' }; // : { name: string; }
let h1 = [1, 2, 3]; // : number[]
let j1 = function test() {}; // : () => void

const a2 = 'May'; // : May
const b2 = 1; // : 1
const c2 = null; // : null
const d2 = undefined; // : undefined
const e2 = true; // : true
const f2 = Symbol('test'); // : typeof f2
const g2 = { name: 'banana'}; // : { name: string; }
const h2 = [1, 2, 3]; // : number[]
const j2 = function test() {}; // : () => void
  • 用 let 宣告,型別推論 a1 的型別為 string
    https://ithelp.ithome.com.tw/upload/images/20240917/20149362wPnsT3fULr.png

  • 用 const 宣告,型別推論 a2 的型別為 May,換句話說 May 是一個 Literal Types
    https://ithelp.ithome.com.tw/upload/images/20240917/20149362YX3pCHsXHa.png

a1a2 值都是 May ,為什麼會有這樣的差異?
這是因為 let 可以被重新賦值的關係,即使初始的值是一個字面量,TypeScript 也會將變數的類型推斷為該字面量的基本類型(例如: stringnumberboolean
const 的值在初始後就不能被改變,因此 TypeScript 會推論此變數值為 Literal Types

Literal Types 的型別註釋

以上試驗的結果並不代表不能用 let 去宣告 Literal Types
透過型別註釋,直接指定字面量的值也是可以做到,看看底下例子吧!

  • x 的 Literal Types 為 hello
let x: 'hello';
x = 'hello'; // ✅ Pass
x = 'typescript'; // ❌ Type '"typescript"' is not assignable to type '"hello"'.
  • 搭配「聯合型別」, flag 的 Literal Types 為 yes or no
let flag: 'yes' | 'no';
flag = 'no'; // ✅ Pass
flag = 'maybe'; ❌  Type '"maybe"' is not assignable to type '"yes" | "no"'.

其實在賦值的時候會貼心跳出提示告訴你,值只能是 'yes' or 'no'
https://ithelp.ithome.com.tw/upload/images/20240917/201493626PeJDYAuhy.png

  • 再進階一點,加入 interface
interface SquareConfig {
  width: number;
}
function setSquare(x: SquareConfig | 'auto') {
  console.log(x);
}
setSquare({ width: 50 }); // ✅ Pass
setSquare("auto"); // ✅ Pass
setSquare("automatic"); // ❌ ...

所以哪些型別有 Literal Types?

object, function, array, tuple, enum...都代表一種型別,廣義來說都是Literal Types的表示方式
可以將 Literal Types 視為「眾多表示方式的集合體」

  • String Literal Types
const str = 'May'; // : May
let str2: 'hello' = 'hello'; // : hello
  • Numeric Literal Types
const n1 = 10; // : 10
let n2: 100 = 100; // : 100
  • Boolean Literal Types
const v1 = true; // : true
let v2: true = true; // : true
  • Object Literal Types
    test1 推論出來的結果並不是 : object,而是 : { ... }
    廣義來說 test1test2 都是 Object Literal Types 的一種
let test1 = { name: 'banana'};  // : { name: string; }
let test2: {name: '11'} = {name: '11'} // { name: '11' }
  • Array Literal Types
let fruits: ('apple' | 'banana')[] = ['apple', 'banana']; // : ('apple' | 'banana')[]
  • Tuple Literal Types
let tuple: ['a', 'b'] = ['a','b'] // : ['a','b']
  • Enum Literal Types
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

let move: Direction = Direction.Up; // : Direction
  • Function Literal Types
function getStatus(): 'success' | 'failure' {
  return 'success';  // 只能回傳 success 或 failure
}

有趣的例子(取自官方文件)

declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method); 

大家覺得上面這段會不會出錯

答案是:「會!」
最後一行 handleRequest(req.url, req.method); 中的 req.method 會被推斷為 string 而不是字面量 GET

有兩種解法可以將型別轉換為 Literal Types

  1. 使用 Type Assertion(型別推斷)
    透過 as 語法來明確指定特定型別
    1-1, 1-2 這兩種方法的目的是要讓 req.method 的型別始終都是字面量 GET
// 1-1
const req = { url: "https://example.com", method: "GET" as "GET" };

// 1-2
handleRequest(req.url, req.method as "GET");
  1. 使用 as const(Const Assertion)
    as const 稱為「常數斷言」,它用來修改型別推斷的結果,確保所有屬性都被指派為 Literal Types,而不是其他型別,例如:string, number
    用在變數宣告或表達式的型別上時,它會強制將變數或表達式的型別視為不可變的(immutable),視為常數
const req = { url: "https://example.com", method: "GET" as const };

💡 as const 介紹

Template Literal Types 樣版字面量/值

Template Literal Types 是 TypeScript 4.1 之後的功能
基於 String Literal Types 的延伸,並且能夠透過「聯合(unions)」來組合出更多字串,增加彈性
想了解聯合型別和交集型別可以看 Day12 - 聯合型別 ( | ) 、交集型別 ( & )

const v1 = 'TypeScript';
const v2 = `hello ${v1}`;

: hello TypeScript 就是 Template Literal Types
https://ithelp.ithome.com.tw/upload/images/20240917/20149362oayi1XiTOp.png

這樣看起來好像有點雞肋對吧...?
其實 Template Literal Types 真正威力在於能夠與「聯合型別(Union Type)」結合,來動態產生字串,
建立高度客製化的型別

以下範例參考官方文件

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

AllLocaleIDs 會被推論為下圖結果
https://ithelp.ithome.com.tw/upload/images/20240917/20149362RVDMteaza3.png

再加上下面這段看看

type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

LocaleMessageIDs 很像細胞繁殖,繁殖到不知道誰是誰了😂
https://ithelp.ithome.com.tw/upload/images/20240917/201493623arDXU8vuq.png

總結

  • Literal Types 是用來「限定變數或參數的型別必須是字面上的值」
  • 廣義來說,object, function, array, tuple, enum 都是Literal Types的表示方式,可將其視為「眾多表示方式的集合體」
  • Template Literal Types 是基於 String Literal Types 的延伸,並且能夠透過「聯合擴展(unions)」來組合出更多字串,增加更多彈性

每天講的內容有推到 github 上喔

References


上一篇
TypeScript 初學者也能看的學習指南 12 - 聯合型別 ( | ) 、交集型別 ( & )
下一篇
TypeScript 初學者也能看的學習指南 14 - Type Assertion 型別斷言
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言