跳至主要内容

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

RN 不是瀏覽器

在上一節中,我們梳理了 React 的核心元件模型:當 stateprops 發生變化時,元件會觸發 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(如 consolesetTimeoutfetch),但像 window.locationwindow.historylocalStorage(在 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:忘記 divdocument 和級聯 CSS。妳是在操作 Host Components。
  • Thread Separation:JS 邏輯與 UI 渲染跑在不同執行緒(Thread)上。JS 跑得再慢,只要不發指令,UI 執行緒通常還是能維持流暢的滾動,這與網頁端「JS 阻塞會導致頁面全卡死」的行為完全不同。

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