十年回首:

React 的過去、現在與未來發展

 

 

 

Zet@WebConf 2024

2024.12.27

  • iCHEF 主任前端工程師

  • 近 11 年前端開發經驗,10 年 React 開發經經驗

  • 曾任 SITCON、JSDC 主議程講者

  • 第 14 屆 IThome 鐵人賽 Modern Web 組冠軍

  • 《React 思維進化:一次打破常見的觀念誤解,躍升專業前端開發者》作者

Zet

周昱安

About me

React 誕生的時空背景

React 誕生的時空背景

  • 2011~2012 時代左右開始,前端網頁不再只是單純的靜態頁面,而逐漸朝向動態、互動式的應用程式邁進
    • 日益複雜的狀態資料以及畫面更新管理
    • 效能需求問題
    • 程式碼重用、管理的需求
  • Facebook 當時也面臨旗下重點產品 Facebook、Instagram 網頁版的開發、管理需求
  • Facebook 對於當時市面上的前端 MVC 框架都不滿意,所以決定自己在內部開發一套解決方案

React 誕生的時空背景

  • 2013 年,Facebook 在 JSConf 2013 發表並開源了 React
  • React 的設計理念著重解決:
    • 重用性:Component-based 設計
    • 狀態管理:單向資料流
    • 畫面更新 & 效能問題:Virtual DOM + Reconciliation

Component-based 設計

React 的單向資料流與畫面渲染策略

單向資料流:資料驅動畫面

  • 畫面結果是原始資料透過模板與渲染邏輯所產生的延伸結果
  • 當資料發生更新時,畫面才會產生對應的更新,以資料去驅動畫面

限縮變因的價值

  • 在單向資料流的設計模式下,資料變動基本上只會來自於開發者手動的觸發資料更新,而畫面結果也只會由原始資料與模板邏輯這兩種變因所構成
  • 限縮變因能帶來的好處:
    • 提高可維護性

    • 提升程式碼的可讀性

    • 減少資料意外出錯的風險

    • 更好的效能優化

實現單向資料流的 DOM 渲染策略

策略一:當資料更新後,人工判斷並手動修改所有

應受到連動更新的 DOM element

  • 優點:

    • 只要開發者 DOM 操作的夠簡潔精準的話,可以盡可能的減少因多餘DOM 操作造成的效能浪費

  • 缺點:

    • 完全依賴人為周全的判斷以及精確的操作 DOM 來維持單向資料流,在複雜的前端應用程式中非常困難

策略二:當資料更新後,一律將整個畫面的 DOM element

全部清除,再以最新的原始資料來全部重繪

  • 優點:

    • 開發者只需要關注模板定義以及資料更新的處理,不需要手動去維護資料連動的畫面操作,要維持單向資料流非常直覺簡單

  • 缺點:

    • 隨著應用程式的龐大與複雜,一律重繪的方式會因為大量不必要的 DOM操作而遇到明顯的效能問題,影響使用者體驗

前端框架的處理策略

  • 無論選擇以上的哪一種渲染策略,都有著明顯且難以解決的缺點

  • 大多數前端框架或解決方案都能透過一些特殊的架構設計來幫助我們解決資料連動畫面更新的需求,在保留這些渲染策略的優點的同時解決其缺點

    • 例如 Vue 採用了 MVVM 的設計模式,藉由 Proxy 監聽來源資料的改變,來自動更新該資料綁定的畫面區塊

Virtaul DOM

DOM 與 Virtual DOM

  • DOM 是一種樹狀資料結構,用於表示瀏覽器中的畫面元素
    • 操作 DOM 會連動瀏覽器的渲染引擎重繪畫面因而效能成本昂貴
  • Virtual DOM 是一種程式設計概念,它透過創建一個虛擬的畫面結構來模擬實際的 DOM,這種虛擬結構會持續同步化到與實際的 DOM,為 UI 管理提供了便利和效能優勢
  • Virtual DOM 並非從實際 DOM 複製而來。Virtual DOM 就像是開發者描繪的畫面試做品,用於先定義期望的畫面結構,然後程式再依據 Virtual DOM 的結構來操作實 際的 DOM,使其與 Virtual DOM 的結構保持一致,所以同步化的方向為由 Virtual DOM ⇒ DOM 單向。

React element

  • React element 是 React 基於 Virtual DOM 概念所實現的虛擬畫面結構元素,因此它是一種普通的 JavaScript 物件資料,用於描述一個預期的實際 DOM element 結構,同時也是作為在 React 中畫面結構描述的最小單位。

  • React element 可以透過呼叫 createElement 方法被建立,並且在經過 React 的處理轉換之後,就能自動產生對應的實際 DOM element。

建立 React element

React 中的一律重繪渲染策略

React 中的一律重繪渲染策略

  • Virtual DOM 這種概念在效能優化上的效益,是當畫面需要更新時,可以透過產生新的 Virtual DOM 畫面結構,然後比較新舊 Virtual DOM 畫面結構上的差異,並根據差異之處來執行最小範圍的實際 DOM 操作,以減少效能成本的浪費

  • 「資料更新後,一律清空畫面再重繪畫面」的 DOM 渲染策略,雖然開發者只需關注模板定義及資料更新的處理,不需手動維護資料連動的 DOM 細節操作,而使得單向資料流的維持變得直覺簡單,但是一律重繪畫面的方式會因為大量且不必要的 DOM element 增刪而造成效能問題,則也是無法忽略的缺點

既然一律重繪實際的 DOM 會導致大量的效能浪費,

那麼就改為一律重繪虛擬的畫面結構資料,也就是 React element

初始畫面的渲染

更新畫面的渲染

更新畫面的渲染

更新畫面的渲染

React 透過 Virtual DOM 概念與一律重繪渲染策略的結合,實現了可預測性高、易於維護且可靠的單向資料流,並同時避免了不必要的大量 DOM 操作所帶來的效能問題

Class Component

Create React App (2016)

React Fiber (2017)

前端對於效能的需求日益增加

  • 前端對於「多任務」處理能力的需求增加
  • 任務的兩大分類
    • CPU 密集處理
    • I/O(data fetch / 使用者互動)
  • 任務之間的依賴關係
  • 任務之間的優先級關係

在 React Fiber 之前...

  • Render phase
    • 由父 component 層層往內部的子 component 呼叫來產生 UI 樹狀結構,形成一個遞迴的 stack
    • 這個 stack 的產生過程是同步且不可中斷的,因此當 render 某一塊樹狀結構過深或過於複雜的畫面時,就有可能佔用瀏覽器的 main thread 過久,導致其他瀏覽器工作產生延遲,影響使用者體驗
  • Commit phase
    • 這部分會呼叫瀏覽器 API 來更新實際的 DOM,因此本來就必定是同步且不可中斷的

可中斷的 render phase

  • 為了解決 render phase 可能佔用 main thread 過久所導致的效能問題,React 15 帶來了全新的底層架構 React Fiber:
    • 以 Linked list 代替一般的樹狀結構的儲存畫面結構
    • 將 render phase 產生畫面的的過程拆解成碎片化、可中斷的流程,透過全新的 scheduler 進行調度

Time Slicing

Concurrent Mode (2018)

React Hooks (2019)

Class Component 的瓶頸

  • 寫法冗長、維護成本高

  • 重用邏輯的困難

  • 與新功能的整合受限

  • 生命週期方法易造成程式邏輯分散

  • 往函式式思維(Functional Programming)發展

Function Component

Function Component 透過一個簡單的 JavaScript 函式回傳 UI,不需要繁複的 class、生命週期方法,也不用手動綁定 this。搭配 Hooks,能讓你在函式裡同時管理狀態與副作用,寫起來更直觀,程式碼結構更簡潔,也更利於重複使用與維護。

Hooks

  • 以函式作為載體,能讓邏輯與流程以最大的彈性被拆分與呼叫。函式可以自由的設計參數與回傳值,也能很好的自由拆分與組合。
  • 依賴於固定的呼叫順序,來避免巢狀呼叫時的命名衝突問題。純粹的函式呼叫並不會有命名衝突問題,它們會自然的形成樹狀結構。而 component 只需要以這些自定義 hook 在層層呼叫所展開後的整體呼叫順序,來區分並追蹤 hooks 的狀態資料即可。

Streaming SSR &

Selective Hydration (2019)

傳統的 Server-Side Rendering

  • Server 端 render 出 HTML 之前,必須獲取所有的資料,全部處理完之後才能產生完整的 HTML 並 response
  • 瀏覽器端 hydrate 任何 element 之前,必須載入完所有的 JavaScript 檔案
  • 在全部 element 都 hydrate 完成之前,使用者無法操作畫面 UI

Streaming SSR

Selective Hydration

  • Suspense 的 code-spliting 在過去是不支援 SSR 的,不過 React 18 開始允許你在 <Posts> 元件載入前就開始 hydrate
  • 等到 <Posts> 的程式碼也載入完成後,才開始 hydrate <Posts> 的部分
  • 透過 fiber 架構的 time-slicing,<Suspense> 內容的 hydrate 行為將不會一直佔用瀏覽器的 main thread,因此這個過程就不會 block 使用者的操作
  • 當有多個 <Suspense> 同時存在時,React 會自動優先 hydrate 使用者操作互動的 UI 區塊

Concurrent Features (2021)

useTransition

  • 用來標記某些更新行為為「可延遲」的狀態
  • 當您有一個會導致重新渲染且「可能卡頓」的大量計算或篩選操作,可以透過 useTransition 來降低狀態更新的優先級,以釋放 main thread 給其他優先級更高的任務
  • Demo 

useDeferredValue

  • useDeferredValue 可以讓我們延遲渲染畫面反應需求比較不緊急的部分,來釋放效能資源給其他優先級更高的任務(例如使用者 input)
  • 類似於讓 state 與畫面的連動更新行為加上 debounce
  • Demo 1(不使用 useDeferredValue
  • Demo 2(使用 useDeferredValue) 

Server Componens

Server Components

  • 優點:
    • 減小 client side 的 bundle size
    • 細緻的效能優化能力
    • 充分運用 server side 的能力
    • 自動 code-splitting
  • 缺點:
    • 對於開發者來說極高的心智負擔與程式碼維護成本
    • 開發環境相當依賴於 meta framework 的支援,例如 Next.js
    • 許多生態系既有的套件將面臨大量的兼容問題

Meta Framework

Meta framework

  • React 的開發環境需求日益複雜,傳統的 Create React App 已經難以承擔這個目標
  • 一些先進或實現性質的功能漸漸開始依賴於 meta framework 的實作配合,例如 Next.js 就與 React 核心團隊就 server components 的支援進行深度合作
  • React 官方文件目前已經不再推薦使用 Create React App 作為專案的環境建置工具,轉而推薦 Next.js 或 Remix 等 meta framework

New React doc (2023)

React 19 (2024)

React 19 的一些新特性

  • 更完整的 server components 支援
  • use() hook
  • 正式支援 useDeferredValue() hook
  • useOptimistic() hook
  • ref as prop

use() hook

React Compiler (2024)

前端 Compiler

  • 前端 Compiler 是用來將前端原始碼(例如 TypeScript、JSX 等)在 build time 時轉換成瀏覽器能夠執行或理解的最終程式碼
  • 常見用途
    • 讓新語法能在舊版瀏覽器上執行(如 Babel 將 ES6 轉成 ES5)
    • 處理非原生語言,例如 TypeScript、JSX 等,轉換成標準的 JavaScript
  • 運作方式
    1. 解析(Parsing):讀取原始碼並建立抽象語法樹(AST)
    2. 轉換(Transforming):按照既定規則或 Plugin 改寫 AST
    3. 輸出(Generating):將改寫後的 AST 產生成目標語言程式碼
  • 優點
    • 可以使用較新語法或語言特性,同時支援舊有瀏覽器
    • 提供更好的開發者體驗(如自動補全、型別檢查等)
    • 效能優化
    • 突破宿主語言的限制
  • 常見工具:Babel、TypeScript 編譯器(tsc)
  • 與其他工具的關係
    • 前端 Compiler 往往和 Bundler(如 Webpack、Vite)結合使用,一起管理與打包前端專案。
    • 前端 Compiler 著重在「語法轉譯」,而 Bundler 著重在「模組化、打包」與優化載入流程。

Prepack

React Forget (2021)

React Forget (2021)

React Compiler (2024)

React Compiler

  • 目前仍在 beta 中,meta 已經將其在內部應用於 Instagram 產品上
  • 有鑒於 JavaScript 本身以及 React 在 runtime 定義 UI 的靈活性,程式碼靜態分析能夠做到的程度相對受限,因此想要讓 React 專案套用 React Compiler 的話,就必須撰寫符合規則的 React 程式碼才能夠正確的被 compiler 辨識並優化
  • 效果
    • 自動化 memo component 或 hooks 的值
    • 自動化 memo useEffect 的 dependencies
    • 自動辨識可以優化的程式碼,如果不行的部分則會避開,以免破壞程式碼在轉譯後的邏輯正確性
  • Effect dependencies 的自動 memo 分析仍有一些問題存在尚未解決,若要使用 React compiler 的話,建議先保留 dependencies 項目對應的 useMemo 或 useCallback 程式碼

React 近幾年的社群發展爭議

React 近幾年的社群發展爭議

  • Meta 對於維護 React 的投入下降
  • 側重 server components 的發展
    • 使得本來學習成本就偏高的 React 更加遠離對開發者體驗友善的方向
    • Next.js 中將 server components 作為 default
    • 生態系中大量套件的兼容問題有可能使得社群發展資源分散甚至分裂
  • 對於 meta framework 的依賴日漸增加,甚至有強綁定的跡象
  • Vercel 對於 React 核心團隊的影響

React 過時了嗎

React 發展至今的包袱與挑戰

  • React 希望貼近 Functional Programming 的思維來設計架構,但宿主語言(也就是 JavaScript)其實並不是一個很純粹的 FP 語言
    • React 最初是設計給 meta 內部的工程師使用的,但發展至今已經是在全世界被大量採用與學習的一門技術
    • 學習成本陡峭,需要理解許多前提觀念才能學好,例如 immutable
    • 效能優化對於開發者的心智負擔高,非常容易誤用
  • Runtime 建立 UI 的設計使得 build time compiler 能夠做到的優化受限
  • 許多其他的新興的前端框架的設計更傾向於開發者的體驗,利用著重 compiler 以及現代化 reactive 的設計來降低開發者在使用時的心智負擔
    • 例如:Svelte、Qwik、SolidJS

參考資料

《React 思維進化》

  • 全台第一本深入 React 核心觀念與思維的技術指南
  • 注重觀念理解,深入剖析 React 的設計以及運作原理
  • 脈絡循序漸進,從零開始堆疊技術思維脈落
  • 全彩印刷
  • 2024 年 1 月天瓏書局單月銷售排行第二

Thanks!

十年回首:React 的過去、現在與未來發展

By tz5514

十年回首:React 的過去、現在與未來發展

  • 543