課程:RN 跨平台開發基礎 第 1 堂:RN 是什麼,怎麼運作的
RN 不是瀏覽器
在上一節中,我們梳理了 React 的核心元件模型:當 state 或 props 發生變化時,元件會觸發 re-render。對於在網頁開發中習慣了 <div>、<span> 和 CSS 的開發者來說,最直覺的反應可能是:「既然 React 邏輯一樣,那 RN 是不是就像一個裝在 APP 殼子裡的網頁瀏覽器?」
答案是:完全不是。
理解這一點,是從「寫出能動的程式碼」進化到「掌握效能與跨平台調優」的最關鍵分水嶺。如果妳把 RN 當成瀏覽器來寫,妳會不斷地在樣式調整、滾動效能和觸控反應上踩到不明所以的坑。這一節,我們要拆解 RN 的渲染路徑,建立起「JS 層」與「原生層」兩層世界的心智模型。
渲染路徑大比拼:網頁 vs React Native
要理解差異,我們必須看這兩個系統在「把程式碼變成螢幕上的像素」時,分別走過了哪些路徑。
網頁端的渲染路徑
在網頁端,妳編寫的 React 程式碼最終是交給**瀏覽器(Browser)**處理的:
- JavaScript 層:React 執行邏輯,產出 Virtual DOM。
- 對接層:React DOM 比較差異(Diffing),然後呼叫瀏覽器的 DOM API(如
appendChild)。 - 瀏覽器層:瀏覽器引擎(如 Chrome 的 Blink 或 Safari 的 WebKit)接收到 DOM 的變動,開始計算樣式、排版(Reflow),最後繪製(Repaint)到螢幕上。
在網頁端,JavaScript 和渲染引擎(Rendering Engine)是高度耦合在瀏覽器內部的,它們有著非常成熟的溝通管道。

React Native 的渲染路徑
RN 的運作邏輯截然不同。妳的手機系統(iOS 或 Android)本身並不具備解析 HTML 或 CSS 的能力,它們只認識自己的原生 UI 元件。
- JavaScript 層:React 執行邏輯,產出 React Element Tree(妳寫的 JSX)。
- Shadow Tree 層(佈局計算):這是 RN 特有的步驟。因為原生系統(尤其是 iOS)並不直接支援網頁常見的 Flexbox 佈局,RN 在後台有一個叫 Yoga 的引擎,它負責將妳在 JS 裡寫的樣式轉化為原生系統能理解的座標與尺寸。
- 原生對接層(UIManager):JS 層將排版好的指令(例如:「在座標 (50, 100) 畫一個寬 200 的藍色區塊」)打包發送給原生層。
- Native UI Tree 層:iOS 的 UIKit 或 Android 的 View 系統接收到指令,生成真正的 原生元件。
- 螢幕:系統渲染引擎將原生元件顯示在螢幕上。
這就是關鍵所在: 在 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 時,妳不應該想像自己在寫一個網頁,而應該想像自己在寫一份**「給原生系統的劇本」**。
- 邏輯在 JS:妳處理資料、狀態、條件判斷。
- 排版在 Yoga:RN 幫妳把網頁習慣的 Flexbox 翻譯成原生座標。
- 渲染在 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。