React:我們即將和後端 API 告別?

語言: CN / TW / HK

2020 年底,React 公佈了一個全新的特性:Server Components,當時它還處於調研和試驗階段,並沒有正式釋出,隨著 React 18.0 版本的正式釋出,Server Component 的腳步聲也越來越近了,不出意外的話,應該會在今年的某個 React 18 的 minor 版本中正式釋出。

Server Components 聽起來好像並不那麼激動人心,React 18 所釋出的各種特性也似乎平平無奇,自從 Hooks 面世已經三年多過去了,React 似乎停滯了前進的腳步,只是在現有的基礎上做些小修小補?

No。

Concurrent rendering(React 18 新帶來的特性)是一種本質上的改變,它本身不像 Hooks 那樣對開發體驗有著近乎翻天覆地的變革,但是這種底層渲染能力/機制的調整,會帶來非常非常多的可能性,例如:

Suspense、OffScreen、Server Components

這三種特性,目前都沒有生產可用,但是等到未來他們正式釋出並漸漸被大面積使用時,每一項特性都會帶來非常顯著的開發體驗的提升。而如果讓我從這些未來會出現的新特性中選一個最期待的,那毫不疑問會是 Server Component。

所以,Server Components 到底是什麼?他會像當年的 Hooks 一樣對整個 React 生態帶來巨大的影響麼?在我們回答這些問題之前,很有必要先解釋一下 Server Components 是什麼,又解決了什麼問題。

注:下文中的很多內容受 Dan 和 Lauren 的這份 演講視訊 [1] 所啟發,如果你想更深入的瞭解即將到來的 React Server Component,那麼非常推薦這段視訊 事實上,這篇文章並不是一份對 Server Components 的用法教學,也不會涵蓋 Server Components 的每一處細節(甚至為了方便表述會有意地略過一些細節),因此, 在讀下文之前,最好是對 Server Components 已經有所瞭解

背景:前後端分離

“前後端分離”是當下主流的 web 研發模式,後端儲存資料,並把對資料的操作(增刪改查)封裝成介面,通過後端服務提供給前端,前端應用傳送請求(例如 http 請求或者 rpc 請求)去呼叫後端提供的介面,從而獲取到資料或者是對資料進行修改。

這可能是十幾年以來非常普遍的研發模式了,也因此,我們被區分成前端開發和後端開發,各自負責著“楚河漢界”的一側。我們在各自那一側都做了非常多的優化、創新、突破,在後端,我們有容器化、微服務、SSR,在前端,我們有 code spliting、前端路由、React Hooks。

但是對於 API 層,我們似乎這麼多年以來都未曾有過關注,即便是有,也僅僅是停留於 API 傳輸效能(例如 grpc)、API 的存在形式(例如 Restful 和 GraphQL)、API 的工程化管理(例如 Postman)。

並非是想說 API 一個邪惡而糟糕的設計,但是自從 Restful 的概念被提出以來,已經 22 年過去了,我們是不是應該在現在重新思考一下:

  • 以網路請求作為前後端的分界是最優解嗎?

  • 如果沒有 API,我們該如何架構和開發 Web 應用?

癥結所在

讓我們再回到剛剛的那張圖,考慮一下 API 在帶來職責分工明晰之外,同時也帶來了哪些問題。

請求瀑布流(Waterfall)

就像 Remix [2] 首頁上所展示的,基於 API 和巢狀路由的前端站點,在請求時會出現瀑布流的現象:

資料的之間可能是有前後的依賴關係,抑或是和元件強耦合在一起,需要等待元件的 bundle 載入完成之後才能發出請求,這些都導致了請求瀑布流現象的出現。

併發請求

後端希望實現小而美的介面,每個介面有獨立的職責,例如:

  • getUser 獲取使用者資訊

  • getSongs?page=12 獲取歌曲列表

  • getNotifactions 獲取通知列表

  • getFavoirateSongs 獲取收藏的歌曲

  • getNewSongs 獲取新發布的歌曲

  • getRecommendSong 獲取今日推薦的歌曲及對應的文案

  • getSearchBarHotKeywords 獲取熱門的搜尋詞

  • getAdBanner 獲取廣告 banner 內容

  • getRecentSongs 獲取最近聽歌記錄

  • getRecommendedPlayList 獲取推薦的歌單列表

  • ……(實在太多了)

每一個介面,單獨拿出來看都是合理的,但是放在一起,就會發現使用者每次開啟這樣一個音樂 web app,都要傳送至少十幾個介面,對於一些稍微複雜一點的網頁,首次載入就需要請求幾十個介面也絲毫不奇怪。

每一個介面的請求,都會帶來網路開銷,甚至在有些環境下會有最大併發請求數量的限制(例如在支付寶客戶端那的 rpc 請求),或許網路層的 automatic batching 可以解決這個問題,但是遺憾的是,在目前的技術體系內,這個問題並不好解決(這裡沒有寫不能解決,是因為的確有一些可行的方案,例如 BFF、依賴閘道器來做介面聚合,但它們都引入的新的問題)。

前端包體積(Bundle size)

包體積已經是“現代”前端開發領域飽受詬病的一點了,動輒幾百 k 的 js 檔案,似乎已經背離了瀏覽器是用來“瀏覽”網頁的初衷了。並不是說我們都要做一個瀏覽器原教旨主義者,但是如果網頁能夠在不損失使用者體驗和開發體驗的前提下,恢復到非常輕量和快速的狀態,難道不是一件好事麼?

協作成本(溝通、邏輯感知和封閉)

在我個人看來,這是大型專案或需要長期維護的應用中最令人頭疼的問題了。

假設我們現在有一個非常巨大的應用,需要有十幾位開發者共同編寫和維護,那如何分工?答案必然是先做模組化,我們把整個應用拆分成幾個彼此儘量獨立的模組,再由每個人或每幾個人負責其中的一個模組。

模組化帶來的好處是邊界清晰(看到一個需求就能判斷出來涉及到哪個或哪些模組做哪些改動)、職責明確(每個人都有自己確定的職責)、減少溝通成本(由於模組內部的邏輯是封閉的,不需要外部感知,所以可以降低溝通成本)。

對於前兩點,目前的前後端分離架構都還是及格的,但對於第三點,我覺得基於網路請求介面的協作模式,在很多情況下並沒有有效地做到邏輯內部封閉、減少需要前後端之間來回溝通的資訊量。

舉個例子,對於這樣的一個頁面:

看起來非常簡單,一些資訊的展示,加上一個充值按鈕,這就是我最開始所設想的。

然而,隨著這個專案不斷的推進,我發現,原本以為是純靜態的標題文案,實際上是需要後端控制的,根據當前使用者的所屬人群來動態判斷文案內容;我發現,由於前端金額計算的可靠性問題,折扣和實際支付相關的內容都是需要在後端預處理之後展示在前端的;

我發現,倒計時的參考時間是需要依靠後端返回的;我發現,按鈕的文案、點選行為,是需要後端控制的,特別是按鈕的點選行為,最終方案是後端返回一個列舉,前端根據這個值來 switch case 一下走不同的邏輯(例如下單、引導先進行註冊和綁卡)……

為了閱讀體驗,我只是列舉了其中隨手想到的一小部分,如果總結一下,那就是,後端和前端並沒有因為“前後端分離”而做到解藕,反倒是藕斷絲連,剪不斷理還亂。後端感知了過多的前端檢視層邏輯,就像是發明了一套 DSL(Domain Specific Language),而前端則是要寫一個針對這套 DSL 的解析器和渲染器。

回到我們剛剛提到的,模組化帶來的好處。模組化能夠降低溝通成本,有一個不可忽略前提,就是架構的合理性。模組化並非是降低溝通成本的本質原因,也並非所有的模組化實踐都能帶來溝通成本的降低。當前後端分離的實踐成為一個僵硬的、死板的“規範”,那它還能真正起到多少降低溝通成本的作用?一個大大的問號。

Server Components

再次申明一下,下文是假設讀者朋友已經對 Server Components [3] 有所瞭解

基於網路請求的 API 模型,有一個大大的前提假設,就是前端應用和後端應用是兩個獨立的應用,但是為什麼一定要是這樣?

或許我們可以讓後端應用直接渲染 HTML,使用者操作時,重新渲染一遍頁面?這其實就是在 Restful 時代之前的架構,有很多弊端,特別是可互動性差,不然也就不會出現後來 Restful 的盛行了。

那再或許,我們可以讓前端的 React 元件,執行在後端?

這就是 React Server Components。

一圖勝千言,在現在的前後端分離模式下,後端提供介面,前端的 React 元件呼叫介面。

而如果後端可以執行 React 元件,直接渲染 React 節點樹到前端,就不需要所謂的 API 的概念了。

後端執行 React 元件並不是什麼新鮮事,我們在 SSR(Server Side Rending)早就習以為常了,但是需要特別註明的一點是,在 SSR 中,後端是運行了 React 元件,生成了一份初始狀態的 html,但這份 html 是沒有可互動性的,它只是為了讓使用者能儘早看到頁面而做的一種改良式的、修修補補一樣的優化。

而 Server Components 所帶來的,是我們可以把同一個專案中,一部分的元件作為 Server Components,另一部分元件,作為 Client Components,因此我們可以既享受到後端內部呼叫帶來的便捷、可維護性,又能保證頁面的可互動性幾乎沒有任何妥協。

如果你用過 PHP 或 Django,那你肯定非常熟悉這種模式:後端直接渲染 html 內容,瀏覽器只負責顯示,使用者點選按鈕,那就重新請求、重新渲染頁面,如果頁面上需要一些複雜的動態互動,比如讓使用者可以把一個列表展開/收起,或者是點選某個按鈕之後展示一個模態框,那可以藉助於 jQuery 來實現。

PHP + bootstrap + jQuery,現在,Server Components 就像是這套正規化的升級版,可以被稱為一種全新的“全棧”開發模式。

因為是在後端環境下,這些 Server Components 可以使用全部的後端能力,不管是中介軟體,還是其他後端微服務的呼叫,甚至是 db 的訪問(當然可以直接跑 SQL,但是更好的實踐是通過一個數據中間層),都可以實現。這樣一來,我們就可以直接把資料從源頭獲取,放到 React 元件的上下文中,那自然就不需要傳統意義上的 API 了。

更準確的說,API 並未消失,我們其實也不會和 API 就此說再見,而是讓它換了一種形式。有模組化的地方,就會有 API,Restful 的 http 網路請求固然是 API,但中介軟體暴露出來的方法,瀏覽器提供的 Date 物件,node 提供的檔案讀取函式,db 提供的 SQL,這些全都是 API。

在這種新架構下,API 變成了後端裡業務應用和上游服務之間的呼叫,變成了 Server Components 和 Client Components 之間的 props 傳遞,前者讓 API 變得更加乾淨、更符合單一職責的原則,而後者讓 API 變得自然到你幾乎感知不到。

所以:

  • Server Components 允許我們不再按照 前端 - 後端 進行模組的拆分,而是依照 業務應用 - 底層服務 來進行更合理的模組拆分。從而可以理論上降低模組之間的溝通成本(因為目前還沒有辦法實踐證明)。

  • 由於 Server Components 是在後端執行元件,直接通過網路傳輸給前端進行渲染,因此很多大體積的包(例如 markdown 渲染、html sanitize)都不需要在前端下載和執行,從而很大程度上降低包體積。

  • 由於底層 db 或上游服務的呼叫都是發生在後端內部的,因此即便出現併發請求,所帶來開銷也遠遠小於前端併發呼叫後端的 Restful API。

  • 同理,請求瀑布流的問題也會因為呼叫開銷降低而消失或減輕。

想象

如果大膽想象一下的話,未來的研發模式可能這樣的:

開發者將不會再區分前端和後端,而是區分為業務應用開發和上游服務開發。現在的後端開發將(真正地)不再需要關注檢視邏輯,只聚焦於底層業務邏輯,為前端提供清晰好用、原子化的服務/介面;而現在的前端開發將會拓展到橫跨前端和後端(程式碼執行環境上),負責的是在後端封裝好的一個個原子化的底層能力上,構建檢視層,而我們也需要一套全新的框架和基礎設施,來適配 Server Components。

目前,Server Components 還沒有正式釋出,而即便正式釋出之後,也還有長長的工程化落地的路要走,Server Components 增加了很多額外的限制,server、client、shared 的區分也可能會帶來一些理解成本。快取、效能、server 重新渲染時的增量更新策略、釋出時的可灰度性和可回滾性、業務中邊界情況的處理,還有很多的問題需要去解決,還有很多的未知尚未被驗證。

參考資料

[1]

演講視訊: https://www.youtube.com/watch?v=TQQPAU21ZUw

[2]

Remix: https://remix.run

[3]

Server Components: https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html

- EOF -

覺得本文對你有幫助?請分享給更多人

關注「大前端技術之路」加星標,提升前端技能

點贊和在看就是最大的支援 :heart: