TypeScript特殊類型與空值安全詳解
本文獻(xiàn)給:
已掌握 TypeScript 基礎(chǔ)類型(string、number、boolean、數(shù)組、元組、枚舉)的開發(fā)者。本文將帶你探索 TypeScript 中更特殊的類型,理解它們的設(shè)計(jì)初衷與使用場景,并掌握處理空值的安全技巧。
你將學(xué)到:
any的弊端與unknown的安全用法void與never的區(qū)別及適用場景null和undefined在strictNullChecks下的行為- 可選鏈
?.與空值合并??的使用 - 非空斷言
!的風(fēng)險(xiǎn)與正確使用方式
一、any —— 危險(xiǎn)的逃生艙
1.1 什么是 any?
any 是 TypeScript 中的頂級(jí)類型,可以表示任意值。被標(biāo)記為 any 的變量會(huì)關(guān)閉所有類型檢查,相當(dāng)于回到了 JavaScript 的動(dòng)態(tài)類型。
let value: any = "hello"; value = 42; // OK value = true; // OK value.toUpperCase(); // OK(運(yùn)行時(shí)可能出錯(cuò))
1.2 any 的弊端
any 雖然靈活,但會(huì)破壞 TypeScript 的類型安全保障:
let data: any = { name: "Alice" };
console.log(data.age.toFixed()); // ? 編譯通過,運(yùn)行時(shí)報(bào)錯(cuò)(Cannot read property 'toFixed' of undefined)
any 還會(huì)污染其他類型,因?yàn)槿魏钨x值都會(huì)導(dǎo)致接收方也變成 any 或失去精確類型:
let dangerous: any = "text"; let safe: number = dangerous; // 不報(bào)錯(cuò),但 safe 實(shí)際是 string,后續(xù)使用可能出問題
1.3 何時(shí)可以容忍 any?
- 快速原型開發(fā)或遷移舊代碼時(shí)臨時(shí)使用
- 第三方庫沒有類型定義且無法立即補(bǔ)全時(shí)
- 某些極其復(fù)雜的動(dòng)態(tài)場景,類型難以表達(dá)
盡量用 unknown 替代 any,保持類型安全。
二、unknown —— 安全的 any
2.1 unknown 的特性
unknown 是 TypeScript 3.0 引入的安全類型,表示“不知道是什么類型”。與 any 不同,unknown 的值不能直接使用,必須經(jīng)過類型收窄(narrowing)后才能操作。
let value: unknown = "hello";
value = 42; // OK
value = true; // OK
// 直接使用會(huì)報(bào)錯(cuò)
value.toUpperCase(); // ? 類型 unknown 沒有 toUpperCase 方法
// 必須先收窄類型
if (typeof value === "string") {
console.log(value.toUpperCase()); // OK
}
2.2 unknown 的使用場景
- 從
any遷移時(shí),先用unknown替代,強(qiáng)制后續(xù)收窄 - 處理 API 響應(yīng)、用戶輸入等不確定來源的數(shù)據(jù)
- 編寫通用工具函數(shù),要求使用者顯式處理類型
function safeParse(input: string): unknown {
return JSON.parse(input);
}
const result = safeParse('{"name":"Alice"}');
// 不能直接訪問 result.name,需要收窄
if (typeof result === "object" && result !== null && "name" in result) {
console.log(result.name); // OK
}
2.3 unknown 與 any 對(duì)比
| 特性 | any | unknown |
|---|---|---|
| 可賦值給任何類型 | ? | ?(需先收窄) |
| 可調(diào)用方法或?qū)傩?/td> | ?(編譯通過) | ?(編譯報(bào)錯(cuò)) |
| 安全性 | 低(繞過類型檢查) | 高(強(qiáng)制類型收窄) |
| 適用場景 | 快速原型、遺留代碼 | 類型未知但需安全處理 |
三、void 與 never —— 函數(shù)的“無返回值”
3.1 void —— 沒有返回值的函數(shù)
void 表示一個(gè)函數(shù)沒有返回值,或者返回 undefined(在嚴(yán)格模式下)。
function logMessage(msg: string): void {
console.log(msg);
// 沒有 return,或 return; 或 return undefined;
}
變量也可以聲明為 void,但只能賦值為 undefined 或 null(非嚴(yán)格模式):
let unusable: void = undefined; unusable = null; // 僅在 strictNullChecks: false 時(shí)允許
注意:在回調(diào)函數(shù)中,如果期望函數(shù)不返回值,應(yīng)標(biāo)注 void,而不是 undefined(因?yàn)?undefined 表示必須返回 undefined)。
3.2 never —— 永遠(yuǎn)不會(huì)返回的函數(shù)
never 表示永遠(yuǎn)不會(huì)正常結(jié)束的函數(shù),例如:
- 拋出異常(函數(shù)執(zhí)行被中斷)
- 無限循環(huán)
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
never 是所有類型的子類型,可以賦值給任何類型;但沒有類型是 never 的子類型(除了 never 自身)。
3.3 void 與 never 的區(qū)別
| 特性 | void | never |
|---|---|---|
| 含義 | 函數(shù)正常結(jié)束,無返回值 | 函數(shù)永遠(yuǎn)不會(huì)正常結(jié)束 |
| 可賦值的值 | undefined 或 null | 無(無法正常返回) |
| 使用場景 | 無返回值的函數(shù)(如事件處理) | 異常拋出、無限循環(huán)、類型守衛(wèi)的窮盡檢查 |
never 在類型守衛(wèi)中的妙用
在 switch 或條件判斷中,可以用 never 做窮盡檢查:
type Shape = "circle" | "square";
function area(shape: Shape) {
switch (shape) {
case "circle":
return Math.PI * 1 ** 2;
case "square":
return 1 * 1;
default:
const _exhaustiveCheck: never = shape; // 如果 shape 類型被擴(kuò)展,這里會(huì)報(bào)錯(cuò)
return _exhaustiveCheck;
}
}
四、null 與 undefined —— 空值處理
4.1 嚴(yán)格空值檢查(strictNullChecks)
TypeScript 的 strictNullChecks 選項(xiàng)(默認(rèn)開啟)嚴(yán)格區(qū)分 null、undefined 和其他類型。
- 開啟后,
null和undefined只能賦值給any、unknown或它們自身的類型。 - 變量聲明時(shí)如果不包含
null/undefined,則不能賦值為這些空值。
// strictNullChecks: true let name: string = "Alice"; name = null; // ? 不能將 null 賦給 string name = undefined; // ? let age: number | null = 30; age = null; // OK
4.2 聯(lián)合類型與空值
常見模式是用聯(lián)合類型允許 null 或 undefined:
let maybeName: string | null = "Alice"; maybeName = null; // OK let maybeAge: number | undefined = 25; maybeAge = undefined; // OK
4.3 可選鏈(Optional Chaining)?.
訪問深層屬性時(shí),如果中間某個(gè)屬性可能為 null 或 undefined,使用 ?. 可以安全地短路返回 undefined。
interface User {
address?: {
city?: string;
};
}
let user: User = {};
// 傳統(tǒng)方式
let city = user.address && user.address.city; // undefined
// 可選鏈
let city2 = user.address?.city; // undefined
可選鏈也支持函數(shù)調(diào)用和數(shù)組索引:
let result = obj.method?.(); // 如果 method 不存在,返回 undefined let item = arr?.[0]; // 如果 arr 是 null/undefined,返回 undefined
4.4 空值合并(Nullish Coalescing)??
?? 運(yùn)算符用于在左側(cè)值為 null 或 undefined 時(shí)返回右側(cè)值,與 || 不同,它不會(huì)將 0、""、false 視為假值。
let value1 = null ?? "default"; // "default" let value2 = undefined ?? "default"; // "default" let value3 = 0 ?? "default"; // 0(不是 null/undefined,保留原值) let value4 = "" ?? "default"; // ""(同上) // 對(duì)比 || let value5 = 0 || "default"; // "default"(|| 會(huì)將 0 視為假值)
4.5 非空斷言(Non-null Assertion)!
當(dāng) TypeScript 無法確定一個(gè)值不為 null 或 undefined,但開發(fā)者確信它一定存在時(shí),可以使用 ! 后綴斷言。
let element = document.getElementById("app")!; // 斷言該元素一定存在
element.innerHTML = "Hello";
// 也可以用于屬性訪問
interface User {
name?: string;
}
let user: User = { name: "Alice" };
let name = user.name!.toUpperCase(); // 斷言 name 存在
風(fēng)險(xiǎn)
! 只是編譯時(shí)斷言,不會(huì)生成運(yùn)行時(shí)檢查。如果斷言錯(cuò)誤,運(yùn)行時(shí)仍可能拋出 Cannot read property of undefined。應(yīng)謹(jǐn)慎使用,優(yōu)先考慮可選鏈或類型守衛(wèi)。
4.6 空值處理最佳實(shí)踐
| 場景 | 推薦方式 |
|---|---|
| 訪問可能不存在的深層屬性 | 可選鏈 ?. |
| 為 null/undefined 提供默認(rèn)值 | 空值合并 ?? |
| 確信某個(gè)值非空但 TypeScript 不認(rèn)可 | 先用 ?. 或類型守衛(wèi),最后才考慮 ! |
| 函數(shù)參數(shù)或返回值允許空值 | 顯式使用聯(lián)合類型 string | null |
| 類型守衛(wèi)收窄 | if (value !== null) { ... } 或 typeof |
五、常見錯(cuò)誤與注意事項(xiàng)
5.1 誤用 any 導(dǎo)致類型失效
let data: any = fetchData(); let result = data.user.name; // 無報(bào)錯(cuò),但可能運(yùn)行時(shí)崩潰
解決:用 unknown + 類型守衛(wèi)。
5.2 將 void 與 undefined 混用
function fn(): void {
return undefined; // OK
}
function fn2(): void {
return null; // ? 嚴(yán)格模式下不能返回 null
}
5.3 非空斷言濫用
let maybeNum: number | null = Math.random() > 0.5 ? 42 : null; let definitelyNum = maybeNum!; // 如果為 null,運(yùn)行時(shí)錯(cuò)誤
解決:使用 if (maybeNum !== null) 或 ??。
5.4 可選鏈與空值合并的混用陷阱
let val = obj?.prop ?? "default"; // 如果 obj 不存在,obj?.prop 返回 undefined,觸發(fā) ?? // 如果 obj.prop 存在但值為 null,也會(huì)觸發(fā) ??
六、綜合示例
// 模擬 API 響應(yīng)
interface ApiResponse<T> {
data?: T;
error?: string;
}
// 處理不確定的響應(yīng)
async function fetchUser(): Promise<ApiResponse<{ name: string }>> {
// 模擬隨機(jī)成功或失敗
const success = Math.random() > 0.5;
if (success) {
return { data: { name: "Alice" } };
} else {
return { error: "Network error" };
}
}
async function handleUser() {
const response = await fetchUser();
// 使用 unknown 安全處理
const unknownData: unknown = response.data;
// 類型收窄
if (unknownData && typeof unknownData === "object" && "name" in unknownData) {
console.log(unknownData.name); // 類型推斷為 string
} else {
console.log(response.error ?? "Unknown error");
}
// 可選鏈與空值合并示例
const userName = response.data?.name ?? "Guest";
console.log(`User: ${userName}`);
}
// never 示例:類型守衛(wèi)窮盡檢查
type Status = "pending" | "success" | "error";
function getStatusMessage(status: Status): string {
switch (status) {
case "pending":
return "Loading...";
case "success":
return "Success!";
case "error":
return "Failed.";
default:
const _exhaustive: never = status; // 如果 Status 增加新值,這里會(huì)報(bào)錯(cuò)
return _exhaustive;
}
}
七、小結(jié)
| 類型 | 含義 | 使用場景 |
|---|---|---|
| any | 任意類型,關(guān)閉檢查 | 臨時(shí)逃生艙,盡量少用 |
| unknown | 未知類型,強(qiáng)制收窄 | 處理不確定數(shù)據(jù),更安全 |
| void | 無返回值 | 無返回值的函數(shù) |
| never | 永不返回 | 異常、無限循環(huán)、窮盡檢查 |
| null | 空值 | 明確表示“沒有值” |
| undefined | 未定義 | 未初始化的變量、缺失屬性 |
| ?. | 可選鏈 | 安全訪問深層屬性 |
| ?? | 空值合并 | 為 null/undefined 提供默認(rèn)值 |
| ! | 非空斷言 | 確信值非空時(shí)使用,需謹(jǐn)慎 |
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
javascript跨域方法、原理以及出現(xiàn)問題解決方法(詳解)
javascript出于安全方面的考慮,不允許跨域調(diào)用其他頁面的對(duì)象。但是在安全限制的同時(shí)也給注入iframe或是ajax應(yīng)用上帶來了不少麻煩??缬蚝唵蔚睦斫饩褪且?yàn)閖avascript同源策略的限制,a.com域名下的js無法操作b.com 或者是c.a.com域名下的對(duì)象2015-08-08
淺談layer的Icon樣式以及一些常用的layer窗口使用方法
今天小編就為大家分享一篇淺談layer的Icon樣式以及一些常用的layer窗口使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09
TypeScript封裝一個(gè)通用的時(shí)間格式化方法
在日常開發(fā)中,我們經(jīng)常需要將時(shí)間格式化為用戶友好的形式,本文將介紹如何在 TypeScript 中封裝一個(gè)通用的時(shí)間格式化方法,需要的可以了解下2025-02-02
js+css完成網(wǎng)站的會(huì)員照片信息顯示效果
js+css完成網(wǎng)站的經(jīng)典效果,一般用于會(huì)員排行榜中,會(huì)員信息的介紹,包括照片等。2010-06-06
JS實(shí)現(xiàn)超精簡的鏈接列表在固定區(qū)域內(nèi)滾動(dòng)效果代碼
這篇文章主要介紹了JS實(shí)現(xiàn)超精簡的鏈接列表在固定區(qū)域內(nèi)滾動(dòng)效果代碼,非常常見的頁面元素屬性變換控制實(shí)現(xiàn)滾動(dòng)效果,簡單實(shí)用,需要的朋友可以參考下2015-11-11
學(xué)習(xí)Javascript閉包(Closure)知識(shí)
這篇文章主要介紹了學(xué)習(xí)Javascript閉包(Closure)知識(shí)的相關(guān)資料,需要的朋友可以參考下2016-08-08
使用canvas實(shí)現(xiàn)仿新浪微博頭像截取上傳功能
用戶提供圖像大小尺寸不合適,如何用截取上傳呢?接下來小編教大家使用使用canvas實(shí)現(xiàn)仿新浪微博頭像截取上傳功能解決問題,需要的朋友一起學(xué)習(xí)吧。2015-09-09
JS如何讓你的移動(dòng)端交互體驗(yàn)更加優(yōu)秀
現(xiàn)在在手機(jī)等移動(dòng)端設(shè)備訪問的人越來越多,我們前端開發(fā)者一直致力于將設(shè)計(jì)稿還原成頁面,供用戶訪問。但除高度還原設(shè)計(jì)稿外,交互上的良好體驗(yàn)也是我們應(yīng)該做到的。2021-05-05

