Typescript: 他實際上沒有驗證你的型別

閱讀時間約 8 分鐘
Typescript 是個好東西: 他讓你定義型別, 確保你的類別和函式保持特定的預期. 他強制讓你去思考你放進函式的資料是甚麼, 你從他得到甚麼. 如果理解錯誤, 試著要呼叫一個預期應該要是 string 的函式, 卻帶著 number 給他, 編譯器就會讓你知道. 所以說他是個好東西.
有時他會讓你誤解: 我遇到一個相信 typescript 保證型別就是你說的那樣. 但我必須告訴你: Typescript 不是這樣做的.
為何? Typescript 是在編譯層運作, 而不是在 runtime. 如果你看一下 Typescript 產生的程式碼長相, 你就會看到他轉換成 Javascript 然後抽離所有型別.
Typescript 程式碼:
const justAFunction = (n: number): string => {
return `${n}`
}

console.log(justAFunction)
Javascript 程式碼的結果(假設你是轉譯成比較近期的 EcmaScript 版本):
"use strict";
const justAFunction = (n) => {
return `${n}`;
};
console.log(justAFunction);
他只會根據你的來源程式碼是否正確來檢查型別. 他不會驗證真實資料.

檢查型別

那 typescript 沒用了嗎? 沒有, 還差得遠. 當你使用的方式正確, 會強制你檢查不確定的型別(不巧的是, 他還提供一些簡單的方式).
稍微改一下範例:
const justAFunction = (str: string[] | string): string => {
return str.join(' ')
}

console.log(justAFunction(["Hello", "World"]))
console.log(justAFunction("Hello World"))
當編譯時, 會導致以下錯誤:
index.ts:2:14 - error TS2339: Property 'join' does not exist on type 'string | string[]'.
Property 'join' does not exist on type 'string'.

2 return str.join(' ')
~~~~

Found 1 error in index.ts:2
編譯器強制認定 str 的變數型別. 其中一個解法是只允許 string[] 進到函式. 另一個解法是驗證變數包含正確型別.
const justAFunction = (str: string[] | string): string => {
if (typeof str === 'string') {
return str
}

return str.join(' ')
}

console.log(justAFunction(["Hello", "World"]))
console.log(justAFunction("Hello World"))
這也會轉譯成 Javascript, 然後型別就被驗證了. 這個情況下, 我們只會保證他是一個 string 而且我們只假設他是個陣列.
在大多數情況中, 這樣已足夠. 但是一旦我們需要處理額外的資料來源 — 像是 APIs, JSON 檔案, 使用者輸入 和 類似的東西 — 我們就無法認定資料的正確. 所以我們應該要驗證資料, 因而有機會確保正確的型態.

讓外部資料對照到你的型別

所以第一步要解決這個問題大概會是建立真實反應你資料的型別.
假設 API 回傳一個使用者紀錄像是這樣:
{
"firstname": "John",
"lastname": "Doe",
"birthday": "1985-04-03"
}
然後我們要為這份資料建立一個 interface:
interface User {
firstname: string
lastname: string
birthday: string
}
然後使用 fetch 從 API 取回使用者資料:
const retrieveUser = async (): Promise<User> => {
const resp = await fetch('/user/me')
return resp.json()
}
這樣做有效, 而且 typescript 會辨識使用者的型別. 但是他可能會騙你. 假設生日包含的是 timestamp 數字(可能對於 1970 以前出生的人會某些問題… 但不是現在的重點). 這個型別仍然會將生日視為一個 string, 雖然確實有數字在裡面… 然後 Javascript 會將他視為數字. 因為, 就像我們所說的一樣, Typescript 不會檢查真實的值.
我現在應該在做甚麼. 寫一個驗證函式. 可能像是這樣:
const validate = (obj: any): obj is User => {
return obj !== null
&& typeof obj === 'object'
&& 'firstname' in obj
&& 'lastname' in obj
&& 'birthday' in obj
&& typeof obj.firstname === 'string'
&& typeof obj.lastname === 'string'
&& typeof obj.birthday === 'string'
}

const user = await retrieveUser()

if (!validate(user)) {
throw Error("User data is invalid")
}
這樣做, 我們可以確保資料是如同他所宣稱的那樣. 但是你可能發現, 這樣做針對更複雜的案例很快就會失控.
有些 protocols 天生處理型別: gRPC, tRPC, 根據 schemaGraphQL 驗證 JSON(到特定程度上). 那些通常是針對特定使用情境. 我們可能需要更 general 的方式.

進入 Zod

Zod 是 Typescript 的型別之間失去的那個連結, 他強制驗證 Javascript 的型別. 他允許你定義結構, 推斷型別與驗證資料.
User 型別會被定義像是這樣:
import { z } from 'zod'

const User = z.object({
firstname: z.string(),
lastname: z.string(),
birthday: z.string()
})
然後型別可以從結構被抽出(被推斷).
const UserType = z.infer<User>
然後驗證會長得像這樣
const userResp = await retrieveUser()
const user = User.parse(userResp)
現在我們有型別和驗證過的資料, 而且我們需要寫的程式碼只比沒有驗證函式的程式碼稍微多一點.

結論

當運用 Typescript, 重要的是知道編譯器檢查和 runtime 驗證的差異. 要確保外部資料符合我們的型別, 我們需要在某處有做驗證. Zod 是很棒的工具, 正好是成本低且彈性的處理方式.
0會員
5內容數
留言0
查看全部
發表第一個留言支持創作者!
Chaol Liu的沙龍 的其他內容
當你買一輛車或腳踏車, 你第一件事會先調整座位的位置和手把的高度到適合你的身體大小. 這就跟 git 配置是一樣的. 在這篇文章, 我會分享一些 git 的設定, 而且是我一直都在使用的.
今天我們引入了 Build Output API, 一個檔案系統為基底的規格, 允許任何框架都可以讓 Vercel 建置, 並且取用了 Vercel 的優勢, 也就是 Vercel 的 infrastructure building blocks, 像是 Edge Functions, Edge M
`npm query` 是從 npm v8.16.0 開始最新的指令, 他接收一個 Dependency Selector(如同在 Dependency Selector Syntax Specification 所定義的) 然後回傳一個從你的專案的 dependencies 篩選過後的 JSON
當你買一輛車或腳踏車, 你第一件事會先調整座位的位置和手把的高度到適合你的身體大小. 這就跟 git 配置是一樣的. 在這篇文章, 我會分享一些 git 的設定, 而且是我一直都在使用的.
今天我們引入了 Build Output API, 一個檔案系統為基底的規格, 允許任何框架都可以讓 Vercel 建置, 並且取用了 Vercel 的優勢, 也就是 Vercel 的 infrastructure building blocks, 像是 Edge Functions, Edge M
`npm query` 是從 npm v8.16.0 開始最新的指令, 他接收一個 Dependency Selector(如同在 Dependency Selector Syntax Specification 所定義的) 然後回傳一個從你的專案的 dependencies 篩選過後的 JSON
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節提供了關於Typescript中流程控制元素的詳細介紹,包括if, else if, else語句,三元運算子,switch語句,各種for迴圈,while迴圈,循環嵌套和控制迴圈語句(break,continue和標籤)的使用。
Thumbnail
從2011年開始,包含江國慶、蘇建和、徐自強與鄭性澤等,部分冤案獲得平反,也讓人開始對司法改革產生希望。然而,我們經常忽略這些案例只是少數,儘管轉變已經開始,仍舊有許多冤案的受難者仍在牢獄中,甚至已經死去,而屬於他們的正義卻依舊遙遙無期。其中,身為台灣現今最高齡死刑犯的王信福,正是其中之一。
Thumbnail
歷久不衰的問題大部分都跟"感情"有關 關於愛不愛這一個問題: 網路上那些觀看次跟搜尋度超高的標題不外乎都是:如果男人有做到這些,表示他愛你!如果你的另一半沒有對你有這些表現表示他不夠愛你! "愛"確實有一個標準存在! 但是同時也不應該拿這套標準去審核每一個人對於愛的表現
Thumbnail
佛說,一切有為法,如夢幻泡影,如露亦如電... 然而,在所有的有為法,那麼多的如露電的幻影中,強求一個人和你在一起,妄求一個人愛你,逼一個人要以你為中心,這是全宇宙十法界中,最愚痴且最累人的事。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節提供了關於Typescript中流程控制元素的詳細介紹,包括if, else if, else語句,三元運算子,switch語句,各種for迴圈,while迴圈,循環嵌套和控制迴圈語句(break,continue和標籤)的使用。
Thumbnail
從2011年開始,包含江國慶、蘇建和、徐自強與鄭性澤等,部分冤案獲得平反,也讓人開始對司法改革產生希望。然而,我們經常忽略這些案例只是少數,儘管轉變已經開始,仍舊有許多冤案的受難者仍在牢獄中,甚至已經死去,而屬於他們的正義卻依舊遙遙無期。其中,身為台灣現今最高齡死刑犯的王信福,正是其中之一。
Thumbnail
歷久不衰的問題大部分都跟"感情"有關 關於愛不愛這一個問題: 網路上那些觀看次跟搜尋度超高的標題不外乎都是:如果男人有做到這些,表示他愛你!如果你的另一半沒有對你有這些表現表示他不夠愛你! "愛"確實有一個標準存在! 但是同時也不應該拿這套標準去審核每一個人對於愛的表現
Thumbnail
佛說,一切有為法,如夢幻泡影,如露亦如電... 然而,在所有的有為法,那麼多的如露電的幻影中,強求一個人和你在一起,妄求一個人愛你,逼一個人要以你為中心,這是全宇宙十法界中,最愚痴且最累人的事。