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

更新 發佈閱讀 9 分鐘

Typescript: It's not actually validating your types. - DEV Community 👩‍💻👨‍💻

raw-image

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 是很棒的工具, 正好是成本低且彈性的處理方式.

留言
avatar-img
Chaol Liu的沙龍
0會員
5內容數
Chaol Liu的沙龍的其他內容
2022/08/15
TransformStream is now supported cross-browser (web.dev) 現在串流轉換在 Chrome, Safari 和 Firefox 都已支援, 他們終於準備好迎接黃金時代!
Thumbnail
2022/08/15
TransformStream is now supported cross-browser (web.dev) 現在串流轉換在 Chrome, Safari 和 Firefox 都已支援, 他們終於準備好迎接黃金時代!
Thumbnail
2022/08/12
當你買一輛車或腳踏車, 你第一件事會先調整座位的位置和手把的高度到適合你的身體大小. 這就跟 git 配置是一樣的. 在這篇文章, 我會分享一些 git 的設定, 而且是我一直都在使用的.
Thumbnail
2022/08/12
當你買一輛車或腳踏車, 你第一件事會先調整座位的位置和手把的高度到適合你的身體大小. 這就跟 git 配置是一樣的. 在這篇文章, 我會分享一些 git 的設定, 而且是我一直都在使用的.
Thumbnail
2022/08/12
今天我們引入了 Build Output API, 一個檔案系統為基底的規格, 允許任何框架都可以讓 Vercel 建置, 並且取用了 Vercel 的優勢, 也就是 Vercel 的 infrastructure building blocks, 像是 Edge Functions, Edge M
Thumbnail
2022/08/12
今天我們引入了 Build Output API, 一個檔案系統為基底的規格, 允許任何框架都可以讓 Vercel 建置, 並且取用了 Vercel 的優勢, 也就是 Vercel 的 infrastructure building blocks, 像是 Edge Functions, Edge M
Thumbnail
看更多
你可能也想看
Thumbnail
賽勒布倫尼科夫以流亡處境回望蘇聯電影導演帕拉贊諾夫的舞台作品,以十段寓言式殘篇,重新拼貼記憶、暴力與美學,並將審查、政治犯、戰爭陰影與「形式即政治」的劇場傳統推到台前。本文聚焦於《傳奇:帕拉贊諾夫的十段殘篇》的舞台美術、音樂與多重扮演策略,嘗試解析極權底下不可言說之事,將如何成為可被觀看的公共發聲。
Thumbnail
賽勒布倫尼科夫以流亡處境回望蘇聯電影導演帕拉贊諾夫的舞台作品,以十段寓言式殘篇,重新拼貼記憶、暴力與美學,並將審查、政治犯、戰爭陰影與「形式即政治」的劇場傳統推到台前。本文聚焦於《傳奇:帕拉贊諾夫的十段殘篇》的舞台美術、音樂與多重扮演策略,嘗試解析極權底下不可言說之事,將如何成為可被觀看的公共發聲。
Thumbnail
柏林劇團在 2026 北藝嚴選,再次帶來由布萊希特改編的經典劇目《三便士歌劇》(The Threepenny Opera),導演巴里・柯斯基以舞台結構與舞台調度,重新向「疏離」進行提問。本文將從觀眾慾望作為戲劇內核,藉由沉浸與疏離的辯證,解析此作如何再次照見觀眾自身的位置。
Thumbnail
柏林劇團在 2026 北藝嚴選,再次帶來由布萊希特改編的經典劇目《三便士歌劇》(The Threepenny Opera),導演巴里・柯斯基以舞台結構與舞台調度,重新向「疏離」進行提問。本文將從觀眾慾望作為戲劇內核,藉由沉浸與疏離的辯證,解析此作如何再次照見觀眾自身的位置。
Thumbnail
本文深入解析臺灣劇團「晃晃跨幅町」對易卜生經典劇作《海妲.蓋柏樂》的詮釋,從劇本歷史、聲響與舞臺設計,到演員的主體創作方法,探討此版本如何讓經典劇作在當代劇場語境下煥發新生,滿足現代觀眾的觀看慾望。
Thumbnail
本文深入解析臺灣劇團「晃晃跨幅町」對易卜生經典劇作《海妲.蓋柏樂》的詮釋,從劇本歷史、聲響與舞臺設計,到演員的主體創作方法,探討此版本如何讓經典劇作在當代劇場語境下煥發新生,滿足現代觀眾的觀看慾望。
Thumbnail
《轉轉生》為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,融合舞蹈、音樂、時尚和視覺藝術,透過身體、服裝與群舞結構,回應殖民歷史、城市經驗與祖靈記憶的交錯。本文將從服裝設計、身體語彙與「輪迴」的「誕生—死亡—重生」結構出發,分析《轉轉生》如何以當代目光,形塑去殖民視角的奈及利亞歷史。
Thumbnail
《轉轉生》為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,融合舞蹈、音樂、時尚和視覺藝術,透過身體、服裝與群舞結構,回應殖民歷史、城市經驗與祖靈記憶的交錯。本文將從服裝設計、身體語彙與「輪迴」的「誕生—死亡—重生」結構出發,分析《轉轉生》如何以當代目光,形塑去殖民視角的奈及利亞歷史。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
Thumbnail
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
Thumbnail
當開發大型Web應用時,TypeScript可以提供靜態類型檢查,幫助開發者捕捉潛在的錯誤。結合Gin,你可以建立強大的、類型安全的後端API。
Thumbnail
當開發大型Web應用時,TypeScript可以提供靜態類型檢查,幫助開發者捕捉潛在的錯誤。結合Gin,你可以建立強大的、類型安全的後端API。
Thumbnail
👨‍💻簡介 在Go中,假如我要判斷一個資料類型是甚麼,該怎麼做呢? Golang有一個功能叫做Type Assertions(類型斷言),它的作用就是能夠在運行時檢查我的資料類型,讓我在傳遞類型時能確保資料類型是正確的。
Thumbnail
👨‍💻簡介 在Go中,假如我要判斷一個資料類型是甚麼,該怎麼做呢? Golang有一個功能叫做Type Assertions(類型斷言),它的作用就是能夠在運行時檢查我的資料類型,讓我在傳遞類型時能確保資料類型是正確的。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News