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 是很棒的工具, 正好是成本低且彈性的處理方式.
avatar-img
0會員
5內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
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
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
※ TypeScript範例說明: interface ITest { test1: string test2: number print: (arg: string[]) => boolean } class Test implements ITest { public te
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
※ TypeScript範例說明: interface ITest { test1: string test2: number print: (arg: string[]) => boolean } class Test implements ITest { public te
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。