前幾天我們一路講到:
null
undefined
""
0
false
這些值在 JavaScript 裡看起來都很容易被歸類成:
「沒有值。」
但其實它們的語意不一樣。
也因此,在前端顯示資料時,很常看到:
value || "-"
value ?? "-"
condition ? A : B
以前我很容易把它們全部理解成:
「反正就是沒有值時顯示預設值。」
但後來才發現:
它們判斷的事情其實不一樣。
||假設:
const discount = 0;
如果畫面寫:
{discount || "-"}
最後會顯示:
-
但我們明明真正想顯示的是:
0
為什麼?
因為 || 會判斷的是:
左邊是不是 falsy。
常見 falsy 值:
false
0
""
null
undefined
NaN
所以:
0 || "-"
會得到:
-
同樣地:
"" || "-"
也會得到:
-
|| 比較像「沒有可用的 truthy 值,就用右邊」例如:
const name = "";
const displayName = name || "N/A";
結果:
N/A
如果這個欄位的需求就是:
空字串也視為沒有內容
那 || 沒問題。
但如果:
0
false
""
本身是合法資料,
就要小心。
?? 只在 null / undefined 時使用預設值例如:
const discount = 0;
const display = discount ?? "-";
結果:
0
因為 ?? 判斷的是:
左邊是不是 null 或 undefined?
只有這兩種狀況才用右邊。
所以:
0 ?? "-"
結果:
0
"" ?? "-"
結果:
""
false ?? "-"
結果:
false
但:
null ?? "-"
得到:
-
undefined ?? "-"
也得到:
-
|| 和 ?? 可以直接看這張表| value | value || "N/A" | value ?? "N/A" |
| --- | --- | --- |
| 0 | "N/A" | 0 |
| "" | "N/A" | "" |
| false | "N/A" | false |
| null | "N/A" | "N/A" |
| undefined | "N/A" | "N/A" |
所以如果:
0
""
false
都是合法資料,
通常:
??
會比:
||
更適合。
假設 API 回:
{
"amount": 0
}
如果:
const text = amount || "N/A";
最後:
N/A
但:
0
明明是一個合法金額。
這時候:
const text = amount ?? "N/A";
就會保留:
0
所以我現在看到數字欄位時,會特別警覺:
0 是不是合法值?
如果是,就不能隨便用 ||。
三元運算子:
condition ? A : B
和:
??
其實不完全是同一類東西。
因為三元運算子不是專門判斷空值。
它是在做:
根據某個條件,決定使用哪個結果。
例如:
const status = isActive
? "啟用"
: "停用";
這裡問的是:
isActive 是 true 嗎?
如果是:
啟用
否則:
停用
例如:
const priceText =
amount != null
? `$${amount}`
: "N/A";
這裡的條件是:
amount != null
也就是:
amount 不是 null,也不是 undefined 嗎?
如果:
amount = 0
條件仍然是:
true
所以會顯示:
$0
這種寫法就很清楚。
?? 和三元有時可以做到類似結果例如:
const text = amount ?? "N/A";
也可以寫成:
const text =
amount != null
? amount
: "N/A";
兩個結果接近。
差別是:
??
→ 專門處理 null / undefined
→ 比較簡潔
三元
→ 可以寫任何條件
→ 彈性更大
我現在會先問:
例如:
customer.name ?? "N/A"
這時:
??
通常最直覺。
例如:
searchKeyword || "全部"
如果需求真的允許:
""
0
false
都視為沒內容,
那 || 才合理。
例如:
status === "ACTIVE"
? "啟用"
: "停用"
或:
amount != null
? `${amount}%`
: "N/A"
這時候三元比較適合。
例如:
const amount = 0;
如果直接:
amount || "N/A"
其實是在偷偷做一個判斷:
0 是不是有效資料?
JavaScript 的答案是:
它是 falsy
但業務需求的答案可能是:
它是合法金額
這時候 JavaScript 的 truthy / falsy 規則,
和產品需求就衝突了。
所以:
不能只看語法方便不方便,要先確認哪些值在業務上是合法資料。
?. 也常常會跟 ?? 一起出現例如:
customer?.company?.name ?? "N/A"
可以拆成:
customer 存在嗎?
↓
company 存在嗎?
↓
name 是多少?
↓
如果結果是 null / undefined
↓
顯示 N/A
所以:
?.
負責:
安全往下取值。
而:
??
負責:
如果最後沒有值,給預設內容。
這兩個常常會一起看到。
假設 UI 欄位:
Tax Rate
Discount
Amount
Quantity
Percentage
這些數字欄位很容易出現:
0
而:
0
通常都有可能是合法資料。
所以看到:
value || "N/A"
我現在會先停一下。
問:
這個欄位如果是 0,畫面應該顯示 0 還是 N/A?
如果答案是:
0
那:
||
就很可能不適合。
例如:
const companyName = "";
產品可能定義:
空字串就是沒有公司名稱。
那:
companyName || "N/A"
反而很合理。
所以:
|| 不一定錯
?? 也不一定永遠最好
真正要看的是:
哪些值在這個欄位裡代表「有效資料」。
可以先用這個判斷方式:
只想防 null / undefined?
↓
??
0 / "" / false 也要當成沒值?
↓
||
有明確條件要判斷?
↓
三元 ? :
例如:
amount ?? "N/A"
name || "N/A"
status === "ACTIVE"
? "啟用"
: "停用"
每一種都在解決不同問題。
看到:
||
??
? :
不要先問:
哪一個比較高級?
而是先問:
我要判斷的是什麼?
null / undefined?
↓
??
truthy / falsy?
↓
||
某個明確條件?
↓
? :
尤其是數字欄位,
看到:
0
時一定要特別小心。
因為:
在 JavaScript 裡它是 falsy,但在業務邏輯裡,它可能是一個非常有效的值。
下一篇可以繼續接另一個很常出現在真實專案的坑:
為什麼 data.customer.name 會突然噴錯?巢狀資料、Optional Chaining ?. 到底是在防什麼?