跳至主要内容

課程:RN 跨平台開發基礎 第 1 堂:RN 是什麼,怎麼運作的

RN 不是瀏覽器

在上一節中,我們梳理了 React 的核心元件模型:當 state 或 props 發生變化時,元件會觸發 re-render。對於在網頁開發中習慣了 <div>、<span> 和 CSS 的開發者來說,最直覺的反應可能是:「既然 React 邏輯一樣,那 RN 是不是就像一個裝在 APP 殼子裡的網頁瀏覽器?」

答案是:完全不是。

理解這一點,是從「寫出能動的程式碼」進化到「掌握效能與跨平台調優」的最關鍵分水嶺。如果妳把 RN 當成瀏覽器來寫,妳會不斷地在樣式調整、滾動效能和觸控反應上踩到不明所以的坑。這一節,我們要拆解 RN 的渲染路徑,建立起「JS 層」與「原生層」兩層世界的心智模型。

渲染路徑大比拼:網頁 vs React Native​

要理解差異,我們必須看這兩個系統在「把程式碼變成螢幕上的像素」時,分別走過了哪些路徑。

網頁端的渲染路徑​

在網頁端,妳編寫的 React 程式碼最終是交給**瀏覽器(Browser)**處理的:

  1. JavaScript 層:React 執行邏輯,產出 Virtual DOM。
  2. 對接層:React DOM 比較差異(Diffing),然後呼叫瀏覽器的 DOM API(如 appendChild)。
  3. 瀏覽器層:瀏覽器引擎(如 Chrome 的 Blink 或 Safari 的 WebKit)接收到 DOM 的變動,開始計算樣式、排版(Reflow),最後繪製(Repaint)到螢幕上。

在網頁端,JavaScript 和渲染引擎(Rendering Engine)是高度耦合在瀏覽器內部的,它們有著非常成熟的溝通管道。

React Native 的渲染路徑​

RN 的運作邏輯截然不同。妳的手機系統(iOS 或 Android)本身並不具備解析 HTML 或 CSS 的能力,它們只認識自己的原生 UI 元件。

  1. JavaScript 層:React 執行邏輯,產出 React Element Tree(妳寫的 JSX)。
  2. Shadow Tree 層(佈局計算):這是 RN 特有的步驟。因為原生系統(尤其是 iOS)並不直接支援網頁常見的 Flexbox 佈局,RN 在後台有一個叫 Yoga 的引擎,它負責將妳在 JS 裡寫的樣式轉化為原生系統能理解的座標與尺寸。
  3. 原生對接層(UIManager):JS 層將排版好的指令(例如:「在座標 (50, 100) 畫一個寬 200 的藍色區塊」)打包發送給原生層。
  4. Native UI Tree 層:iOS 的 UIKit 或 Android 的 View 系統接收到指令,生成真正的 原生元件。
  5. 螢幕:系統渲染引擎將原生元件顯示在螢幕上。

這就是關鍵所在: 在 RN 裡,JavaScript 只是「指揮官」,而「士兵」是手機系統內建的原生控制項。

為什麼是「原生」?這對妳的 APP 意味著什麼​

當妳在代碼中寫下 <View> 時,這不只是一個類似 <div> 的標籤。在底層,RN 的渲染引擎會根據平台進行「轉譯」:

  • 在 iOS 上,它會建立一個 UIView 物件。
  • 在 Android 上,它會建立一個 android.view.View 物件。

當妳寫下 <Text> 時,它對應的是 iOS 的 UITextView 和 Android 的 TextView。

原生帶來的「質感」差異​

為什麼有些網頁包殼的 Hybrid App(例如早期的 Cordova 或一些簡單的 WebView App)用起來總是有一種「假假的」感覺?因為瀏覽器是在模擬 UI。

但在 RN 中,因為妳使用的是真正的原生元件,妳會自動獲得以下好處:

  • 滾動的阻尼感(Momentum Scrolling):iOS 和 Android 的捲動慣性計算是不一樣的。RN 呼叫的是原生列表元件,所以當妳在 iPhone 上滑動時,那種絲滑感和手指放開後的減速曲線,與系統內建的設定 App 完全一致。
  • 觸控回饋:Android 的波紋效果(Ripple Effect)和 iOS 的變暗回饋,是系統層級的行為。
  • 輔助功能(Accessibility):視障人士使用的螢幕閱讀器(如 VoiceOver)能精確識別出 RN 的元件,因為它們在本質上就是系統元件,而不是瀏覽器裡的一堆像素。

對於妳開發的內容與媒體類 APP 來說,這一點至關重要。讀者在閱讀文章或滑動媒體列表時,那種「隨手感」就是來自於這層原生的底層支援。

消失的 Web 概念:為什麼妳找不到 document 和 CSS​

一旦理解了 RN 不是瀏覽器,妳就會明白為什麼許多在網頁開發中理所當然的東西,在 RN 裡消失了。

沒有 DOM,所以沒有 document​

在 RN 中,不存在一個全域的 document 物件。妳不能執行 document.getElementById('my-header'),因為根本沒有 HTML DOM 樹。 如果妳需要操作某個元件,妳必須使用 React 的 useRef。這強迫開發者必須遵守 React 的聲明式(Declarative)開發範式,而不是手動去修改 UI。

沒有瀏覽器全域物件​

雖然 RN 為了方便開發者,模擬了一些 Web API(如 console、setTimeout 和 fetch),但像 window.location、window.history 或 localStorage(在 RN 中需使用 AsyncStorage 等替代方案)這些與瀏覽器環境深度綁定的 API 是不存在的。

樣式不是 CSS 檔案​

在網頁中,CSS 是由瀏覽器引擎解析的字串。但在 RN 中,樣式是 JavaScript 物件。 妳沒有 .css 檔案,也沒有級聯(Cascading)的概念。妳不能寫 div > p 這種選擇器。每個元件的樣式通常是明確定義並直接注入的。這雖然犧牲了 CSS 的靈活性,但也避免了網頁端常見的「樣式污染」問題——這在大型專案中反而是種優勢。

我們將這類在 RN 中能直接使用的原生組件稱為 Host Components。它們是 JS 世界與原生世界的連接點。

效能的代價:跨越邊界的成本​

既然原生元件這麼強大,為什麼有些 RN APP 還是會卡頓?

這裡我們要引入一個關鍵概念:兩層世界是獨立的。

  • JS 層 運行在一個名為 JavaScriptCore (或 Hermes) 的引擎中。
  • 原生層 運行在 Objective-C/Swift 或 Java/Kotlin 環境中。

想像 JS 層是住在「邏輯島」上的策劃人員,而原生層是住在「UI 島」上的施工隊。兩島之間沒有橋,只能靠**船(Bridge)**通訊。

每當妳需要更新 UI,JS 層就必須把指令序列化(Serialized)成 JSON 格式,放上船,運過海,原生層拆封後再執行。

  • 如果這條航線上擠滿了船(例如妳在 onScroll 裡瘋狂 setState,導致每秒傳送幾百次更新指令),通訊就會延遲。
  • 這就是為什麼「大量數據傳輸」或「頻繁的 JS-to-Native 互動」會成為效能瓶頸。

實戰案例:為什麼媒體列表在 RN 比較順?​

以妳的媒體類 APP 為例,如果用 Web 技術做長列表,瀏覽器必須負責維護成千上萬個 DOM 節點的記憶體與排版,這對手機瀏覽器負擔很大。 而在 RN 中,當妳使用 FlatList 時,它底層調用的是 iOS 的 UITableView 或 Android 的 RecyclerView。這些原生控制項擁有極度優化的回收機制:當一個項目滾出螢幕時,它的原生實體會被回收並重新利用於新出現的項目。 這種效能等級是純網頁端很難輕易達到的,這也是為什麼 RN 雖然有通訊成本,但在 UI 表現上依然能遠超 Web App 的原因。


建立兩層世界的心智模型​

總結來說,開發 React Native 時,妳不應該想像自己在寫一個網頁,而應該想像自己在寫一份**「給原生系統的劇本」**。

  1. 邏輯在 JS:妳處理資料、狀態、條件判斷。
  2. 排版在 Yoga:RN 幫妳把網頁習慣的 Flexbox 翻譯成原生座標。
  3. 渲染在 Native:最終呈現給使用者的是貨真價實的 iOS/Android 元件。

關鍵要點​

  • Native Fidelity:RN 提供的是原生元件,具備原生的手感、阻尼與觸控回饋。
  • No Web APIs:忘記 div、document 和級聯 CSS。妳是在操作 Host Components。
  • Thread Separation:JS 邏輯與 UI 渲染跑在不同執行緒(Thread)上。JS 跑得再慢,只要不發指令,UI 執行緒通常還是能維持流暢的滾動,這與網頁端「JS 阻塞會導致頁面全卡死」的行為完全不同。

了解了這兩層世界的運作原理後,下一個自然的疑問就是:那這兩層世界到底是怎麼溝通的? 為什麼有的 APP 滾動起來還是會卡?為什麼新出的 RN 架構(JSI)能讓效能大幅提升?下一部分,我們將深入探討那座連接著兩座島嶼的關鍵建築——Bridge,以及它如何演進為更強大的 JSI。