跳至主要内容

課程:JavaScript 與 React 底層原理 第 9 堂:ES6+ 語法收尾

28 安全存取語法

身為 JavaScript 開發者,你一定遇過這個堪稱「百萬美元錯誤」的紅字報錯:TypeError: Cannot read property 'xxx' of undefined。當你的程式碼試圖從一個不存在的物件中讀取屬性時,整個應用程式會直接崩潰。在過去,我們被迫寫出長長的邏輯門檻來保護程式碼,但現代 JavaScript 提供了更優雅、更安全的「防護罩」。

消失的鏈結:Optional Chaining (?. )

想像你正在開發一個電子商務網站,你需要從 API 獲取使用者資料並顯示他們居住的城市。資料結構可能長這樣:

const user = {
id: 1,
profile: {
name: "Aria",
address: {
city: "台北",
street: "忠孝東路"
}
}
};

要在畫面上顯示城市,你可能會直覺地寫下 user.profile.address.city。但現實總是殘酷的,API 回傳的資料可能因為使用者沒填寫資料、或是資料載入中,導致 profileaddressnullundefined

傳統的「邏輯門神」模式

在 ES2020 之前,為了防止程式碼崩潰,我們必須使用 && 運算子進行層層檢查:

// 傳統寫法:冗長且難以閱讀
const city = user && user.profile && user.profile.address && user.profile.address.city;

console.log(city); // "台北" 或 undefined

這種寫法本質上是利用了 && 的特性:如果左側為 Falsy,就停止往右執行。雖然有效,但當層級變深時,程式碼會變得像「火車軌道」一樣冗長,閱讀體驗極差。

預測:如果我們中間斷掉會怎樣?

如果 user.profilenull,而你依然執行 user.profile.address,JavaScript 引擎會發出抗議。現在,讓我們看看 Optional Chaining(選擇性鏈結) 如何優雅地解決這個問題。

// 現代寫法:簡潔有力
const city = user?.profile?.address?.city;

console.log(city);

短路求值(Short-circuiting)的原理

?. 的核心機制在於「短路」。當 JavaScript 執行到 ?. 時,它會檢查左側的值:

  1. 如果左側是 nullundefined,運算會立即停止,並直接回傳 undefined
  2. 如果左側有值(不論是什麼,只要不是空值),則繼續往右讀取屬性。

這就像是在每一層存取之前都放了一個自動感應門,如果沒人(空值),門就不會打開,後面的程式碼也就不會被執行,成功避免了噴錯。

不只是讀取屬性:函數與陣列

Optional Chaining 的威力不僅限於物件屬性,它還能處理更複雜的場景:

1. 安全的函數呼叫

在 React 中,我們常會透過 Props 傳遞回調函數(Callback)。如果父元件忘記傳入該函數,直接呼叫會導致崩潰。

// 如果 props.onSuccess 存在就執行,不存在就不執行且不報錯
props.onSuccess?.(data);

2. 安全的陣列存取

當你不確定一個陣列是否存在,或是該索引位置是否有值時:

const firstItem = data?.list?.[0];

這裡要注意語法是 ?.[],這在處理動態 Key 值或陣列時非常有用。

專家提醒:不要過度依賴 ?.

雖然 ?. 很方便,但請記住它是一個「靜音器」。如果你在所有地方都加上 ?.,可能會掩蓋掉原本應該被發現的 Bug。

  • 建議: 只在你「預期」資料可能是空值的地方使用(例如:API 回傳值、選填的 Props)。
  • 警告: 絕對不要在賦值左側使用,例如 user?.name = "Bob" 是語法錯誤。

聰明的預設值:Nullish Coalescing (??)

當我們使用 ?. 拿到了 undefined 之後,下一步通常是:「如果拿不到資料,我該顯示什麼預設值?」

|| (OR) 的陷阱

長久以來,JavaScript 開發者習慣使用 || 來設定預設值:

const settings = {
speed: 0,
title: ""
};

const currentSpeed = settings.speed || 10;
const currentTitle = settings.title || "預設標題";

console.log(currentSpeed); // 預測:0? 現實:10 (喔不!)
console.log(currentTitle); // 預測:""? 現實:"預設標題"

為什麼會這樣?因為 || 的邏輯是:只要左邊是「Falsy」值,就回傳右邊。 在 JavaScript 中,0""falseNaN 全部都是 Falsy。這在處理數值計算或字串輸入時,會造成嚴重的邏輯錯誤——使用者明明輸入了 0(這是一個有效的速度),系統卻把它當成沒有輸入,硬塞給他 10

救星登場:Nullish Coalescing (??)

Nullish Coalescing(空值合併運算子) 的出現,就是為了修正 || 的這個「過度熱心」的行為。

?? 只有在左側為 Nullish(僅限 nullundefined)時,才會回傳右側的值。

const currentSpeed = settings.speed ?? 10;
const currentTitle = settings.title ?? "預設標題";

console.log(currentSpeed); // 0 (正確!)
console.log(currentTitle); // "" (正確!)

為什麼這對 React 很重要?

在 React 中,狀態(State)初始化常會用到這個技巧。假設你有一個計數器元件:

function Counter({ initialCount }) {
// 如果 initialCount 是 0,使用 || 會變成 10,這是不正確的
const [count, setCount] = useState(initialCount ?? 10);

return <div>{count}</div>;
}

使用 ?? 可以確保當父元件傳入 0 時,計數器能正確從 0 開始,而不是跳到預設值。


強強聯手:建立安全資料管線

在實戰中,我們通常會將這兩個語法組合起來,形成一條堅不可摧的資料存取鏈結。這在處理後端 API 的深層巢狀資料時尤其強大。

實戰場景:顯示使用者地點

假設我們正在處理一個複雜的 API 回應,我們想要顯示城市名稱,若沒有資料則顯示「未知城市」。

const apiResponse = {
data: {
user: {
profile: null // 使用者還沒填寫個人資料
}
}
};

// 組合應用
const cityName = apiResponse?.data?.user?.profile?.address?.city ?? "未知城市";

console.log(cityName); // "未知城市"

這行程式碼的執行邏輯如下:

  1. apiResponse?.data:存在。
  2. ?.user:存在。
  3. ?.profile:是 null
  4. 短路發生: 因為遇到 null,之後的 ?.address?.city 全部被跳過,整個左側表達式回傳 undefined
  5. undefined ?? "未知城市":因為左側是 undefined?? 運算子發揮作用,回傳右側的 "未知城市"。

整個過程沒有拋出任何 Error,且邏輯清晰易讀。


在 React 中的防禦性編程模式

身為一名資深的 React 開發者,你會發現這兩個語法幾乎無處不在。以下是幾個標準的應用模版:

1. 防止 Loading 狀態崩潰

當資料還在 Fetch 時,data 通常是 null。如果你直接 .map,頁面會白屏。

function UserList({ data }) {
// data 可能還是 null,這裡使用 ?? 提供空陣列作為後路
const list = data?.results ?? [];

return (
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}

2. 選填配置項的預設值

在封裝 UI 組件(如 Modal 或 Button)時,我們會有很多可選的設定。

const Modal = ({ config }) => {
const isClosable = config?.allowClose ?? true; // 預設可以關閉
const themeColor = config?.style?.color ?? "#333";

// ...
};

3. API 錯誤處理的簡化

有時候 API 出錯會回傳一個包含錯誤訊息的物件,有時候則是空的。

const errorMessage = response?.error?.message ?? "發生未知錯誤,請稍後再試";

進階細節:語法限制與優先順序

在使用這些語法時,有兩個小陷阱需要注意:

1. 混合使用時的括號限制

出於安全考慮,JavaScript 不允許在沒有括號的情況下將 ?? 直接與 &&|| 混用。

// ❌ 語法錯誤 (SyntaxError)
const value = a && b ?? c;

// ✅ 必須明確指定優先順序
const value = (a && b) ?? c;

這是因為引擎不想幫你猜測你想先算哪一個,透過強制加括號,你的程式碼意圖會變得更明確。

2. ?. 對於「未宣告變數」無能為力

?. 只能檢查物件的「屬性」是否為空,但它無法檢查「變數本身」是否存在。

// 假設某個變數完全沒有用 const/let 宣告過
console.log(missingVar?.prop); // ❌ ReferenceError: missingVar is not defined

這再次提醒我們,這些語法是用來處理「資料結構的不確定性」,而不是用來修復「程式碼邏輯的錯誤」。


總結與銜接

掌握了 Optional Chaining 與 Nullish Coalescing,你就擁有了一套強大的「資料防護罩」。它們不只是讓程式碼變短,更重要的是它們體現了**宣告式編程(Declarative Programming)**的精神:你是在描述「我想要什麼樣的資料結果」,而不是在寫「如何一步步檢查資料是否存在」。

這與 React 的哲學不謀而合。React 讓我們專注於 UI 的狀態,而這些現代語法讓我們能安全、穩定地從複雜的資料流中提取出這些狀態。

核心要點回顧

  • ?. (Optional Chaining) 解決了層層檢查 null/undefined 的痛苦,透過「短路」機制防止崩潰。
  • ?? (Nullish Coalescing) 修正了 || 會誤判 0"" 的問題,提供更精準的預設值設定。
  • 兩者結合是 React 處理 API 資料與可選 Props 的標準做法。

下一步預告

現在我們已經學會了如何模組化組織程式碼(ESM),也掌握了如何安全地存取深層資料。接下來,我們要進入 Topic 5 的最後一個高峰:「函數式陣列操作(map, filter, reduce)」

當你利用 data?.list ?? [] 安全地拿到一組陣列後,該如何利用這些函數式工具將資料轉換為 JSX 列表?這將是將 JS 邏輯轉化為 React 畫面最關鍵的最後一哩路。我們下一節見!