跳至主要内容

課程:RN 跨平台開發基礎 第 5 堂:Navigation 架構入門

Navigation 設計邏輯

在上一章節中,我們深入探討了樣式系統與佈局邏輯。如果說樣式與元件是 APP 的「外殼」與「皮膚」,那麼 Navigation(導覽系統) 就是 APP 的「骨架」與「神經系統」。

你可能已經在現有的兩款媒體 APP 中使用了 react-navigation,並且能透過 navigation.navigate('Detail') 讓頁面跳轉。但你是否曾遇到過這些奇怪的現象:為什麼我按下返回鍵後,前一個頁面的狀態還在?為什麼有時候頁面切換會感覺到一絲延遲?為什麼推播通知點進去時,堆疊順序會亂掉?

要解決這些問題,不能只靠 AI 改程式碼,我們必須從底層邏輯出發,理解 React Native 的導覽系統是如何運作的。

網頁路由 vs. 行動端導覽:本質的範式轉移

對於有網頁開發背景的人來說,導覽通常等同於 URL Routing。但在行動端(Native APP),這是一個完全不同的世界。

網頁:基於「地址」的切換 (Address-based)

在瀏覽器中,導覽的核心是「網址列」。當你從 /home 點擊連結到 /profile 時,瀏覽器基本上是做了一次「替換」。雖然現代單頁應用程式(SPA)如 React 網頁版會試圖優化這個過程,但其底層邏輯依然是:

  1. 匹配網址:看 URL 對應到哪個元件。
  2. 銷毀舊頁面:將 /home 的 DOM 節點從瀏覽器中移除。
  3. 掛載新頁面:渲染 /profile 的內容。

在網頁中,頁面與頁面之間通常是「平級」且「排他」的——同一時間通常只有一個路由節點存在。

APP:基於「堆疊」的層疊 (Stack-based)

行動端的導覽邏輯更像是 「一疊撲克牌」。當你從首頁點進去一篇新聞詳情時,首頁並沒有「消失」,它只是被壓在了新聞詳情頁的「下面」。

  • Push(推入):你把一張新牌放到了最上面。
  • Pop(彈出):你把最上面的牌拿掉,露出了原本就在下面的那一張。

這種**堆疊(Stack)**的概念決定了行動端導覽的兩個關鍵特性:

  1. 狀態持久性:當你從詳情頁返回首頁時,首頁的滾動位置、輸入框裡的文字、甚至是正在播放的影片進度,通常都應該保持原樣。這是因為首頁的元件實例(Instance)從未被銷毀(Unmount),它只是在後台「待命」。
  2. 層次感與手勢:因為頁面是疊在一起的,所以 iOS 才能實現「從螢幕左邊緣向右滑」來返回的效果。這個手勢本質上是在物理上「撥開」上面的圖層。

理解了這一點,你就能明白為什麼在 RN 中,頁面的生命週期(Lifecycle)比網頁複雜得多。在網頁中,離開頁面就是 componentWillUnmount;但在 RN 中,離開頁面可能只是它「失去焦點(Blur)」了,它依然活在記憶體裡。

為什麼 React Native 核心不內建 Navigation?

這是一個讓很多初學者困惑的問題:既然 Navigation 這麼重要,為什麼 Meta (Facebook) 不把它寫進 React Native 的核心庫(Core)裡面?

1. 保持核心輕量與靈活性

React Native 的哲學是提供一個「橋接(Bridge)」框架,讓 JS 能控制原生元件。Navigation 涉及到極其複雜的原生 UI 行為(如 iOS 的 Navigation Controller 和 Android 的 Fragment 管理),這些行為在不同版本、不同平台上差異極大。

如果將導覽邏輯鎖死在核心庫中,React Native 的更新速度會被原生作業系統的變更拖累。透過將其外包給社群,開發者可以根據需求選擇最適合的方案。

2. 「JS 模擬」與「原生導覽」的路線之爭

在 React Native 生態中,歷史上出現過兩大主流派系:

  • React Navigation (JS 實作):這是你目前正在使用的工具。它的邏輯完全由 JavaScript 驅動,使用 Animated 庫來模擬原生的過場動畫。
    • 優點:極度靈活、易於自定義、跨平台表現高度一致。
  • 缺點:所有的計算都在 JS Thread 進行,如果 JS 線程塞車,動畫可能會卡頓(雖然在新架構下這已大幅改善)。
  • React Native Navigation (Wix 實作,原生導覽):這套庫會直接調用 iOS 的 UINavigationController 和 Android 的 Activity
    • 優點:效能與原生 APP 完全一致,不受 JS 線程卡頓影響。
  • 缺點:配置極其複雜,需要頻繁修改原生 Java/Objective-C 程式碼,且靈活性較低。

對於絕大多數內容與媒體類 APP 而言,React Navigation 因為其易用性與強大的社群支援,已經成為了事實上的工業標準(De Facto Standard)。

React Navigation 的三大核心概念

要掌握導覽系統,你必須在腦中建立起這三個實體的分工圖:

1. Navigator(導覽器/容器)

Navigator 是管理者。它決定了頁面之間如何切換、動畫長什麼樣子、以及它們具備什麼樣的層次關係。 React Navigation 提供了幾種預設的 Navigator:

  • Stack Navigator:最常用的,像疊紙牌一樣。
  • Tab Navigator:底部標籤分頁,用於平級的功能切換。
  • Drawer Navigator:從側面拉出來的抽屜選單。

一個 APP 通常是由多個 Navigator 嵌套(Nesting) 而成的。例如,你的根部可能是一個 Stack,裡面包含一個登入頁面和一個 Tab Navigator,而 Tab Navigator 裡的每一個分頁又各自是一個 Stack。

2. Screen(頁面/元件)

Screen 就是你寫的 React 元件。它是導覽系統中的「內容節點」。當你將一個元件註冊為 Screen 時,React Navigation 會自動注入兩個重要的 props 給它:

  • navigation:這是你的「行動手冊」,裡面有 navigate()goBack()push() 等方法,讓你發出導覽指令。
  • route:這是你的「身份證」,裡面包含了當前頁面的名稱,以及從上個頁面傳過來的參數(params)。

3. Navigation State(導覽狀態)

這是最抽象但最重要的部分。React Navigation 其實是在 JS 層維護了一個巨大的 JSON 物件,用來描述目前的導覽結構。

一個簡化的導覽狀態看起來可能像這樣:

{
"index": 1,
"routes": [
{ "key": "home-123", "name": "Home" },
{ "key": "detail-456", "name": "Detail", "params": { "id": 99 } }
]
}
  • routes 陣列就是目前的「堆疊」。
  • index 告訴系統哪一張牌現在在最上面。

當你調用 **navigation.navigate('Detail')** 時,你本質上是在命令這個「狀態機」去更新這個 JSON 物件。 一旦狀態改變,React Navigation 就會根據這個新狀態,決定要渲染哪個元件,並啟動對應的原生過場動畫。

模擬機制:JS 層如何模擬原生行為?

React Navigation 被稱為「JS-based Navigation」,這意味著它並不依賴於 iOS 或 Android 內建的路由導航控制器,而是自己在畫面上佈局。

它是如何「騙」過使用者的?

當你執行切換動作時,React Navigation 會在 JS Thread 計算動畫數值(例如:頁面 X 軸從 100% 移動到 0%),然後將這些數值傳遞給原生渲染層。

這種模擬機制帶來了一個關鍵的優點:跨平台的一致性。你可以讓 Android 擁有跟 iOS 一模一樣的「由右向左滑入」動畫,而不必受限於 Android 系統預設的「由下向上淡入」。

然而,這種模擬也帶來了挑戰:

  1. 記憶體管理:因為舊頁面沒有銷毀(為了能隨時返回),如果你的 APP 堆疊太深,且每個頁面都載入了大量高解析度圖片,記憶體佔用會飆升。這就是為什麼在開發媒體類 APP 時,我們需要謹慎處理圖片的快取與釋放。
  2. 事件衝突:有時候 RN 的滑動返回手勢會跟你的橫向捲動列表(ScrollView)衝突。這時就需要對導覽系統的手勢配置進行微調。

回歸 Aria 的實戰經驗:為什麼有時候會「怪怪的」?

回顧你開發過的兩款 APP,是否曾遇過:在 A 頁面修改了資料,返回 B 頁面後,B 頁面顯示的還是舊資料?

這是因為在 Web 邏輯中,你會預期返回 B 頁面時,B 會重新 componentDidMount。但在 RN 的 Stack 導覽中,B 頁面一直都在那裡,它根本沒有「重新掛載」,所以你的 useEffect(() => { ... }, []) 不會再次執行。

這種時候,你就不能依賴組件掛載(Mounting),而必須依賴 Navigation Events。React Navigation 提供了 focusblur 事件,讓你知道頁面何時真正回到了使用者的視線中。這就是「理解 Stack 狀態」與「單純依賴 React 生命週期」的差異。

另外,如果你發現跳轉頁面時有明顯的「掉幀」或「白屏」,通常是因為你在頁面切換的瞬間,同時執行了大量的 JS 邏輯(例如格式化大型 JSON 資料)。因為導覽動畫也是 JS 驅動的,這會導致動畫與資料處理爭奪同一個線程的資源。

連結到未來

在接下來的部分,我們將會拆解最核心的 Stack Navigator。你會學到 navigatepush 到底差在哪裡(這對處理「相關推薦」這種無限循環的跳轉場景至關重要),以及如何精準地控制堆疊的重置(Reset),這在處理登入登出邏輯時是必備的技能。

關鍵要點與展望

總結

  • 堆疊 (Stack) vs. 地址 (URL):行動端導覽是頁面的層疊,舊頁面通常不會被銷毀,而是保留在記憶體中。
  • JS 驅動的模擬:React Navigation 透過 JS 狀態機來模擬原生動畫,提供了極大的自定義空間,但也需要注意效能管理。
  • 導覽狀態機:所有的跳轉本質上都是對一個 JS 物件(Navigation State)的修改,理解這個物件的結構是解決導覽 bug 的關鍵。
  • 生命週期差異focus 不等於 mount。在 Stack 結構下,頁面回到視線不一定會觸發重新掛載。

在掌握了 Navigation 的設計哲學後,我們就能更踏實地進入實作細節。下一部分,我們將深入剖析 APP 中最常用的骨幹:Stack Navigator,徹底釐清那些讓導覽邏輯變得混亂的 Actions。