跳至主要内容

[Day13] TS:什麼!這個 typeof 和我想的不一樣?

typeof operator

這是我們今天要聊的內容(不是業配),老樣的,如果你已經可以輕鬆看懂,歡迎直接左轉去看我同事 Ken 精彩的文章 — 「From State Machine to XState」。

Typeof Operator in JavaScript

多數 JavaScript 的開發者都知道,在 JS 中有 typeof operator 可以使用,使用上雖然有一些要留意的細節,但基本上 typeof 可以幫開發者判斷某個變數的型別:

// https://javascript.info/types#type-typeof

typeof undefined; // "undefined"
typeof 0; // "number"
typeof true; // "boolean"
typeof 'foo'; // "string"
typeof (() => {}); // "function"
typeof null; // "object"

Typeof Type Operator in TypeScript

但今天我們要聊的是 TS 中的 Typeof Type Operator,多一個 type 差很多。

這個 typeof 的用法其實非常簡單,我們知道在 TypeScript 中,即使沒有針對變數定義型別(Type Annotation),它還是會自動幫我們推導出該變數對應的型別,只要把滑鼠移到該變數上方,就可以看到 TS 自動幫變數推導出其可能的型別:

Type Inference

這裡 TypeScript 會自動把 conference 這個「變數」推導為如下的「型別」:

const conference: {
name: string;
year: number;
isAddToCalendar: boolean;
website: string;
};

但這個被自動推導出來的型別需要滑鼠移上去時才看得到,有沒有什麼方式能夠把這個「自動推導的結果」建立成一個可以被使用的型別呢?

有,就是 Typeof Type Operator,它的語法一樣就是 typeof ,後面可以接「變數」或「屬性」:

type Conference = typeof conference;

這個建立出來的型別 Conference 就會是剛剛一開始滑鼠移到變數 conference 上時顯示的結果:

Typeof Type Operator

那回到今天最開始的例子:

const conference = {
name: 'MOPCON',
year: 2021,
isAddToCalendar: true,
website: 'https://mopcon.org/2021/',
};

type ConferenceKeys = keyof typeof conference;

搭配在第 4 天時對 keyof 的了解,讀者應該可以想到 ConferenceKeys 的結果會是什麼。因為 typeof conference 是物件型別,所以 keyof typeof conference 就會是把所有該物件型別所有的 key 取出組成 Union Type:

Typeof Type Operator

讀者也許會好奇,不能直接用 keyof conference 就好,一定要在 conference 前面加上 typeof 嗎?學習 TS 的重點之一就是拿掉看看會發生什麼事,現在就讓我們拿掉原本的 typeof 看看:

Typeof Type Operator

這時候 TypeScript 報錯了,錯誤內容是:

'conference' refers to a value, but is being used as a type here. Did you mean 'typeof conference'?

意思是,conference 是 value,但它卻被當成 type 來使用。為什麼會有這個錯誤呢?

還記得 conference 本身是一個 JavaScript 中的變數而不是 TypeScript 中的型別嗎?因為 keyof 後面只能接的是「型別(Type)」,不能接 JavaScript 的「值(value)」,因此,一定會需要透過 typeof 先把 JS 的變數值轉成 TS 的型別後,才能使用 keyof 這個 operator。

區分 JavaScript 和 TypeScript 的 typeof

其實...也沒什麼好區分的。

雖然這兩個的語法都是 typeof,但執行的時間點是完全不同的,JavaScript 的 typeof operator 會在程式執行時(runtime)呼叫,而 TypeScript 的 typeof type operator 則只有在 TypeScript 中「操作型別」時會用到,一旦把 .ts 編譯成 .js 後,到了 runtime 會無影無蹤(畢竟 JS 原生並沒有這種強型別的概念)。

可以分辨的出那個是 JavaScript 的 Typeof Operator,那個是 TypeScript 的 Typeof Type Operator 嗎?

const conference = {
name: 'MOPCON',
year: 2021,
isAddToCalendar: true,
website: 'https://mopcon.org/2021/',
};

type Conference = typeof conference;
const typeofConference = typeof conference;

補充:把 enum 的所有 key 取出成 union types

還記得在 Day11 中曾提到,透過 Template Literal Types 可以把 enum 的 values 取出變成聯集嗎?

enum MANUFACTURE {
APPLE = 'apple',
SAMSUNG = 'samsung',
GOOGLE = 'google',
SONY = 'sony',
}

type Manufacture = `${MANUFACTURE}`; // "apple" | "samsung" | "google" | "sony"

現在如果是想要把 enum 的 key 取出變成 union types 的話,則可以使用到今天提的 Typeof Type Operator:

// Get all keys of enum
type ManufactureKeys = keyof typeof MANUFACTURE; // "APPLE" | "SAMSUNG" | "GOOGLE" | "SONY"

是不是很酷又很方便啊!現在讀者就可以根據需要取得 enum 的 keys union 或 values union 了。

範例程式碼

https://tsplay.dev/mAV3RW @ TypeScript Playground

工商時間

筆者今年將會在 MOPCON 2021 分享主題「用 Type 建立 Type:一起來當個 TypeScript 的型別魔術師」,提供給對 TypeScript 有興趣,又覺得每天追文章實在太辛苦的你來參考看看!但即使報名了 MOPCON 還是可以繼續追蹤鐵人賽啦!

MOPCON2021

參考資料