iT邦幫忙

2024 iThome 鐵人賽

DAY 18
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 18

TypeScript 初學者也能看的學習指南 18 - undefined、null 型別

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240927/20149362odPqorR6hP.png

本篇文章除了要來介紹 undefinednullstrictNullChecks 的不同設置下的差異
還會介紹到「兩者的可指派性」和 「TypeScript 的特殊語法 - 非空斷言操作符(!)」

🔗 本文有提到的其他型別,文章連結整理在這


undefined、null

null 和 undefined 都是 JavaScript 的原始型別 (primitive type)
在 TypeScript 中,undefined、null 的行為會由 tsconfig.json 中的 strictNullChecks 是開啟還是關閉而有不同

strictNullChecks off (false)

  • strictNullChecks 預設為關閉
// tsconfig.json 
"compilerOptions": {
   "strictNullChecks": false,
   // ... 略 ...
}
  • 當關閉 strictNullChecks 時,任何型別都可以被賦值為 null 或 undefined
let str: string = null; // ✅ Pass
let num: number = undefined; // ✅ Pass
  • 若沒有特別註釋型別,變數 a 和 變數 b 接會被推斷為 any
let a = null;
let b = undefined;

https://ithelp.ithome.com.tw/upload/images/20240928/20149362WCr7vwKxjY.png

strictNullChecks on (true)

  • 開啟 strictNullChecks。如果 stricttrue 時,strictNullChecks 會自動被啟用
// tsconfig.json 
"compilerOptions": {
   "strictNullChecks": true,
   // ... 略 ...
}
  • 變數 a 和 變數 b 會被分別推斷為 undefinednull
let a = undefined; // : undefined
let b = null;      // : null
  • null 和 undefined 僅能賦值給它們自己,其他的型別則會報錯
let str: string = null; // ❌ Type 'null' is not assignable to type 'string'.
let num: number = undefined; // ❌ Type 'undefined' is not assignable to type 'number'.

strictNullChecks on,想定義變數為 null 或 undefined 時,怎麼做才不會報錯?

我們已經知道當開啟 strictNullChecks 時,null 和 undefined 僅能賦值給自己,但其實也可以利用「聯合型別」(如 string | null 或 number | undefined)來巧妙避開檢查機制

想了解「聯合型別」,歡迎看之前寫的文章
🔗 聯合型別和交集型別

function doSomething(x: string | null) {
  if (x === null) {
    // ... 略 ...
  } else {
    console.log(x.toUpperCase()); // ✅ Pass
  }
}

如果環境狀況允許,官方建議把 strictNullChecks 打開,以防非預期錯誤發生


Non-null Assertion Operator 非空斷言操作符

非空斷言操作符(!)是 TypeScript 的特殊語法,在任何表達式後加上 !,表示會忽略對 nullundefined 的型別檢查,告訴 TypeScript :「我知道值不會是 null 或 undefined」

以下方例子來說,當操作變數的時候,就算有給定初始值,編譯時仍然會報錯,因為 a 還是有機會會出現 null

function test(a: string | null = 'apple') {
  console.log(a.toUpperCase()); // ❌ 'a' is possibly 'null'.
}

如果要避免以上情況發生,除了可以用 typeof, instantOf 等來 narrowing type,還有更簡潔的方式就是加上 !

function test(a: string | null = 'apple') {
  console.log(a!.toUpperCase());   // ✅ Pass
} 

function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());  // ✅ Pass
}

💡 ! 不能濫用
如果在 ! 的使用上過於大意,可能在運行時,會出現 TypeError 錯誤
建議使用「條件檢查」或 「Optional Chaining ?.」相比於使用 ! 更安全

function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());  // ✅ Pass
}

// 傳入 null 或 undefined,會在運行時報錯 ❌ TypeError: Cannot read properties of null (reading 'toFixed')

liveDangerously(null);
liveDangerously(undefined);

undefined 和 null 的可指派性(assignability)

https://ithelp.ithome.com.tw/upload/images/20240928/20149362RKPV8rzzUD.png
圖片來源
又是這張從 Day16 就開始出現的圖,不知道有沒有人跟我一樣始終覺得這張圖看起來不怎抹直觀

橫軸是「目標型別」;縱軸是「來源型別」
藍色勾勾(✓):來源型別可以被指派給目標型別
綠色勾勾(✓):tsconfig.json 中的 strictNullChecks 關閉時才能被指派

這裡就先專注看 undefined 和 nul 就好,這張圖想告訴我們

  1. undefined
  • 可以指派給 any、unknown、void 和 undefined
  • 無法指派給 object、null 和 never
  1. null
  • 可以指派給 any、unknown、null
  • 無法指派給 object、void、undefined 和 never

官方文件上還補充道

  • 當 strictNullChecks 關閉時,null 和 undefined 的行為與 never 類似,它們可以指派給大多數型別,但大多數型別無法指派給它們,畢竟 never 是屬於 the bottom type(底層型別),越底層的型別,能夠接受的範圍越窄

  • 當 strictNullChecks 開啟時,null 和 undefined 的行為與 void 類似,它們無法被指派給其他型別,也無法將其他型別指派給它們,除了 any、unknown 和 void 外(undefined 始終可以賦值給 void)

以下範例是 strictNullChecks 開啟時會報的錯

let strVal = 'hi';
let nullVal = null;
let undefinedVal = undefined;

strVal = nullVal;       // ❌ Type 'null' is not assignable to type 'string'.
nullVal = strVal;       // ❌ Type 'string' is not assignable to type 'null'.
undefinedVal = strVal; // ❌ Type 'strVal' is not assignable to type 'undefined'.
undefinedVal = nullVal; // ❌ Type 'null' is not assignable to type 'undefined'.

以上其實也不用硬背,單純順手把表格的內容整理一下,踩坑踩多了自然慢慢就會記起來了


總結

  • 若環境允許,建議把 strictNullChecks 都設為開啟,更為安全
  • 非空斷言操作符 (!)是 TypeScript 的特殊語法,在告訴編譯器說:「可以忽略 null 或 undefined 的型別檢查」,要謹慎使用,它可能造成運行時出現 TypeError
  • 當 strictNullChecks 關閉時:null 和 undefined 可以賦值給任何型別
  • 當 strictNullChecks 關閉時:未明確註釋型別的變數,若賦值為 null 和 undefined ,則會被推斷為 any
  • 當 strictNullChecks 開啟時:null 和 undefined 僅能賦值給自己或利用如聯合型別來賦值,否則會報錯

每天的內容有推到 github 上喔


References


上一篇
TypeScript 初學者也能看的學習指南 17 - void 型別
下一篇
TypeScript 初學者也能看的學習指南 19 - never 型別
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言