我曾用Angular重寫了jQuery應用,如今卻被React逼瘋了

作者 | mbrizic
譯者 | 平川
策劃 | 冬梅
古老的 Angular
年輕的時候,我曾用 Angular.JS 賺錢。當時,那是一項非常出色的技術。絕對是當時最大的 JS 框架,最重要的是,那可能是 Web 開發第一次有了“框架”。在那之前,用的都是“庫”,所以它是第一個,不僅提供一套函式讓你使用,還提供給你一個構建 Web 應用的實際框架。
但事物總是相對的,Angular 之所以好是因為它之前的技術不夠好。當時,我們還有其他的 SPA 框架,比如 Backbone 和 Knockout,但它們沒有產生太大的影響。不,Angular 真正擊敗的敵人是 jQuery。
儘管 jQuery 只是 HTML DOM API 的一個封裝(不得不承認當時非常粗糙),但它仍然成了構建複雜 Web 應用的事實標準。它的工作原理相當簡單:你在 JS 中手動或透過命令建立 HTML 元素,然後修改它們,移動它們,做任何你需要做的事情,使網站可以像應用程式一樣互動。
對於簡單應用來說,這完全沒問題,但如果應用比較大,維護就會變成一場噩夢。當時就開始發生這樣的事情了。你真不能責怪 jQuery,只能責怪現代使用者的需求,他們需要無處不在的互動性。所以開發人員被迫繼續使用 jQuery,儘管它已經不再適合這項工作了。
然後 Angular 出現了,一切都解決了。你可以專注於編寫 UI 和應用邏輯,而不是手動組裝 HTML 的各個部分。它確實是一個改變遊戲規則的框架(不再是一個庫),你終於有一個合適的工具來建立大型互動式應用了。它有一些神奇的東西:
A) 元件。這個命名很奇怪,實際上,應該稱它們為“指令(directives)”,但無論如何,你可以定義一個簡單的 HTML 和 JS 檔案組合,作為 UI 的一個組成部分,然後在應用的多個地方重用它。
B) 雙向繫結。你定義一個變數,每當它改變時,UI 中的所有地方都會更新。這很有效。後來,人們開始抱怨這種全向資料流不好,所以有人推動使用單向(自上而下)繫結,這在技術上聽起來更好,但實際上讓一切變得更復雜,並引發了一場討論,最終,如今的我們不得不使用 Redux。
在我的第一份工作中,我正好參與了一項將一個龐大且難以管理的 jQuery 應用重寫為 Angular 應用的工作。不管是過程還是最終結果都相當好。
然而,不好的是,幾年後我們不得不用 Angular 2 重寫同樣的 UI,我只是慶幸自己離開那家公司足夠早,不然他們會讓我用 React 第三次重寫它
加入 React 開發
後來,我確實有機會學習 React,甚至在一兩個專案中專業地使用它了。
我仍然記得第一眼看到它時的新鮮感。當時,對比的是 Angular 2——它徹底重寫了先前的版本,但樣板程式碼翻倍了。開箱即用的 TypeScript、單向繫結、響應式 / 可觀察模式,它們本身都是好東西,但太複雜了:開發慢、構建慢、執行慢。
React 重回簡單之路,獲得了人們的支援。有一段時間,React 保持了簡單性,變得越來越受歡迎,成了開發 SPA 的首選庫。
是的,我們現在又用了“庫”這個詞,為的是顯示它實際上有多簡單。但你沒法只用一個庫來構建一個複雜的應用。你需要幾個庫來處理應用的所有問題,你還需要一些程式碼結構。React“自帶啤酒”的方法意味著你基本上是自己構建了一個框架,並帶著它所有的缺點。
最終的結果是,沒有兩個 React 應用是一樣的。每個人都有一個定製的“框架”,由從網際網路上隨機找到的庫構建而成。
我當時不幸參與的應用都讓我有同樣的想法——即使是 Angular 2 也會比這好。JSX“核心”似乎總是看起來很堅固,但它周圍的一切都是一團糟。
所以我退出了,去寫一些 Java 後端,我相信這說明了一切。
就在我以為我退出了的時候…
就在我自以為已經退出了的時候,我最近又回到了 React。
當然,這只是一個業餘專案,所以我沒有把它當一個嚴肅的生產應用來對待。但即便如此,這次經歷不僅證實了我的低預期沒問題,而且還大大拉低了我的預期。React 讓人抓狂,不知道為什麼沒有人談論它。
架構、元件、狀態
首先,讓我們從 React 為你選定的架構開始。正如前文提到的,React 只是一個庫,所以它不會強迫你做任何事,但是,JSX 的隱含限制使得一些模式自然而然地浮現出來。很久以前,我們談論過 MVC、MVVM、MVP,它們都是同一主題的不同變體,那麼 React 是哪一個呢?我認為都不是,我認為這是一種新的正規化——我們可以稱之為“基於元件的架構”。
乍一看,一切都是合乎邏輯的。你有元件,你構建了一個自上而下的元件樹,然後砰的一聲,你的應用就完成了。React 施展了一些內部魔法,確保與你提供給它的資料保持同步。這足夠簡單了。
但在這個過程中,它有時候表現得比它應該有的樣子更聰明。對於一個簡單的“UI 庫”來說,React 確實有很多複雜的術語。而且對於一個與“函數語言程式設計”無關的庫來說,它裡面確實有很多函數語言程式設計的命名。
讓我們從狀態開始。如果你有一個自上而下的元件樹,那麼邏輯上,你會希望將狀態從上往下傳遞。但在實踐中,由於小元件非常多、非常混亂,所以你需要花費大量的時間和程式碼來連線各種資料片段,以便將它們放置在需要的地方。
這個問題透過使用 React 鉤子將狀態“側載(sideloading)”到元件中得到了解決。對此,我還沒有聽到有人抱怨過,但你們是認真的嗎?你們是在說任何元件都可以使用任何部分的應用狀態嗎?更糟糕的是,任何元件都可以發起狀態更改,然後可以在任何其他元件中更新。
這怎麼可能透過程式碼審查?基本上,你使用的是一個全域性變數,只是狀態修改規則更復雜。它們甚至不是規則,而只是一種儀式,因為沒有什麼能真的阻止你從任何地方修改狀態。人們真的認為,如果你給某樣東西起一個聰明的名字,比如 reducer,它突然就變成了好的架構嗎?
所以,如果自上而下和側載方法都不行,那麼這個問題的解決方案是什麼?我真不知道。事實上,我唯一能想到的是:如果我們不能很好地解決這個問題,那麼也許整個“元件架構”就是一個錯誤,我們就不應該稱之為優秀設計的典範,並停止創新。也許,這次我們真的需要另一個 JS 框架,嘗試一些更好的東西。
React 鉤子
接下來我們討論下 React 鉤子。不可否認它們很有用,但它們的存在至今仍讓我頭疼不已。
人們如何把元件說成是 “純函式”,卻又把鉤子說成是元件內部的一個有狀態的小黑盒。鑑於元件的可組合性,它更像是一層又一層套在一起的有狀態的小黑盒。
這一點就不說了,我主要想吐槽下 useEffect。我們看一個很簡單的“副作用”。你改變了狀態,然後你需要做一些外部操作,比如將結果傳送給一個 API。理論上,這種將“重要的應用程式內容”和“副作用”分開的做法是有道理的。但在實踐中,你能像這樣乾淨利落地分開嗎?
首先,我最不滿意的是 useEffect 被用來“在元件掛載後執行某物”。我理解,當 React 從類遷移到鉤子時,這是最接近 componentDidMount 的替代品,但是拜託,這無論如何都是一種很不規範的做法。
使用一個“副作用”鉤子來初始化元件?好吧,如果你必須從那裡進行 API 呼叫,我會同意那是副作用。但是那個 API 呼叫,它也會設定狀態。所以,一個完全無害的“副作用”鉤子實際上管理了元件的狀態。為什麼沒有人談論這有多瘋狂?
而且,如果想依賴那個狀態並在之後做一些事情,你就要定義另一個 useEffect,並且依賴於第一個鉤子的設定。
這段程式碼來自最近被幾千萬美元收購的一家公司的一個生產應用。我稍微修改了一下,使用了更簡單的 House 和 Cat 實體,而不是實際的內容。但請看一下,試著解析這段程式碼的執行順序。當你準備好了,可以看下下圖給出的答案:
像這樣的一連串的狀態變化,本來用簡單的命令式程式碼就可以實現,現在卻分散在兩個非同步函式中,唯一能提示執行順序的是每個函式底部的“依賴關係陣列”。而實際上,你在心裡是從下到上解析它的。
我記得,當初人們因為 then 方法而嫌 JavaScript promises 笨重,甚至在此之前我們還有“回撥地獄”——但任何東西都比現在這個要好。
我知道,這些問題可以透過以下方式解決:a) 將它們移到一個單獨的檔案中(這只是在隱藏問題);或者 b) 可以使用 Redux 或其他方法(這方面我沒有足夠的經驗,並不是很確定)。
“模式”
所有這些加在一起看上去很醜,也違背了 React 在其“Hello world”示例中承諾的簡單性。但等等,我還沒說完。有個熟人寫過一篇博文,標題是“最常見的 React 設計模式”。我不知道自己在期待什麼,但對於這些模式的複雜性以及要弄清楚其機制所需花費的腦力,我感到非常震驚——所有這些只是為了在螢幕上顯示一個專案列表。
最令人震驚的是:文章並沒有承認這一點。所有這些複雜性都被認為是理所當然的。顯然,人們真的就是這樣構建他們的 UI,而且沒有人對此感到驚訝。
這好像還不夠,有些人甚至去寫“CSS-in-JS”,並以此獲取報酬。我同意,JSX 最初的“關注點分離”並不是“檔案分離”,將 HTML 和 JS 寫在同一個檔案中實際上沒有問題。但是把 CSS 也放進去,並且使其成為強型別的?這是不是太過分了?
為什麼
我們不能只是說 React 瘋了,然後就不管了。作為理性的靈長類動物,我相信我們可以做得更好。我們可以嘗試理解它。
我想起了我的第一份工作,想起了 “jQuery 遷移 ”專案中的一位同事。他是一位經驗豐富的後端工程師,也是一位架構師,總的來說,是一個在軟體領域非常受人尊敬的人。
關於他,我印象最深的不是他提供的技術解決方案,而是他對我們的前端工作的判斷。比如,看著我們的 Angular 應用,他會說類似這樣的話——你們這裡到底是要做什麼?為什麼非得搞得這麼複雜?
這並不是說我們很差勁——我們也是一個對軟體毫不含糊的團隊。只是在當時,以一個傳統的後臺開發人員的眼光來看,整個 Angular 的設定看起來簡直是瘋了。
如今,我大概和他當時的年紀一樣大,我在這裡寫一篇博文,講述 React 有多麼瘋狂。我想,有些事情是不可避免的。
但讓我們更上一層樓,試著理解下為什麼會這樣。
首先,我認為我們都會同意,大多數 Web 應用本來就不應該成為 Web 應用。人們即使不需要 SPA,也會選擇 SPA,他們以後可能需要,所以顯然,從一開始就選擇 SPA 花費不會太多。
但我想說的是,事實上,這種做法確實會讓你付出代價。我們如此深陷於“預設 SPA”的方式,以至於我們忘記了替代方案有多簡單。擁有一個簡單的伺服器端渲染頁面比考慮一下 React 都要簡單許多。沒有 API 通訊開銷,前端非常輕量級,UI 程式碼可以是強型別的(如果後端是強型別的),你可以在整個技術棧上進行重構,什麼都載入得更快,你可以更好地進行快取,因為有些元件是高度靜態的,對所有使用者來說都一樣,所以只需要渲染一次,諸如此類。
確實,你失去了在產品經理心血來潮時提供複雜互動邏輯的靈活性。但可能這也不全對,因為我敢打賭,你可以用簡單的 JavaScript 進行“漸進式增強”,直到很長時間後才真正需要新增 React 狀態管理的複雜性。
我說的是,我們使用 React 只是因為我們以前使用過它。這也難怪,惰性是一劑猛藥,但這仍然無法解釋為什麼這段程式碼最終會複雜到難以想象的地步。
令人驚訝的是,這個問題的答案讓我停止了對 React 的抨擊,走向了相反的方向,不僅為 React,也為 Angular 和 jQuery 以及它們之前的一切辯護了起來。我認為,程式碼之所以糟糕,是因為開發一個互動式 UI,其中任何元件都可以更新任何其他元件,這簡直是在軟體開發中能做的最複雜的事情之一了
想想你在日常生活中使用的任何其他系統。廚房水槽有冷熱兩個輸入,一個輸出。廚房攪拌機或電鑽可能有一個或兩個按鈕,但無論你做什麼,都隻影響可旋轉部分的行為。烤箱可能有三、四、五個旋鈕,並且可能有同樣數量的輸出,這聽起來已經很危險了。
相比之下,互動式 WebUI 可能有無限數量的輸入以及無限數量的輸出。你怎麼能指望它有“乾淨的程式碼”呢?
因此,關於 React 的這番長篇大論,其實根本不是 React 的錯。也不是 Angular 和 jQuery 的錯。簡單來說,無論你選擇哪種技術,都不可避免地會在構建響應式使用者介面時那不可能實現的複雜性下崩潰。
如何解決這個問題?
我不夠聰明,也沒有深入研究,真的解決不了這個問題,但我可以提出一些想法。如果我們採用這種輸入 / 輸出的心理模型,將網頁視為一個實際的東西,那麼我們也許可以從減少它的輸入和輸出數量入手。
在輸入方面,我說的是:“去減少按鈕的數量”,這可能無法強制執行。但毫無疑問,功能越少,程式碼庫就越容易管理。
這很簡單,不值得一提,是嗎?產品經理知道嗎?增加三個按鈕會比增加兩個多 5% 的 Bug,並且,未來在那個螢幕上開展的設計和實現工作複雜度會增加 30%。沒有人度量這些事情,但我相信那可能是真的。
如果我告訴你需要在後端新增 Redis,你會告訴我“不,我們需要控制技術複雜性”——如果產品經理要求新增一個應用範圍的全域性過濾器,可以從任何地方應用於任何東西,你就只會低下頭,編寫一些人們將來要花費 10 年時間去設法擺脫的怪物,為什麼?
簡而言之——請不要再增加那麼多按鈕了,我求你了。我知道,你甚至可以瘋狂地移除一些?
然而,在輸出方面,情況就有點不同了。寫這篇文章的時候我意識到,基本上,伺服器端渲染的頁面就是將頁面減少到一個輸出。不管你與它做任何互動,它都只是重建整個頁面。具有諷刺意味的是,這意味著移除 FP(函數語言程式設計)風格的 React,伺服器端渲染的頁面實際上成了狀態的一個純函式。沒有前端狀態 = 簡潔性大獲全勝,如果你能負擔得起的話。
不可避免地,當你確實需要在伺服器端渲染的“應用”中新增一些指令碼邏輯時,明智的做法也許是隻在最必要的地方新增,越少越好。
關於這一點,我認為“互動島”這個名字不錯。我谷歌了一下,結果發現 這個名已經被用了。不過,那篇文章還提到了 Preact、SSR、清單檔案,所以我不確定我們的看法是否一致。人們會把一切都搞得過於複雜。
但我確實相信,我們如今有足夠的頻寬來載入一個小型 React 應用,它只渲染一個位於傳統伺服器端渲染頁面裡的互動島。我覺得這種組合不會那麼糟糕,但我還沒有嘗試過,在我的下一個專案裡,我可能會嘗試一下。
所以,我未經測試的擁有乾淨且可維護前端程式碼的方法是:全部在伺服器上渲染,只在你真正需要的地方插入 React 或任何其他東西。
它真的不可能比現在更糟了。
原文連結:
https://mbrizic.com/blog/react-is-insane/
宣告:本文由 InfoQ 翻譯,未經許可禁止轉載。
點選底部閱讀原文訪問 InfoQ 官網,獲取更多精彩內容!
今日好文推薦
Cursor終結者?Grok 4正式登頂!馬斯克揚言程式設計碾壓,20萬N卡年賺47億美金!
16 年老程式設計師用 Claude Code 搞副業:我只手敲了 1000 行,剩下 95% 程式碼靠自動生成
180 天狠賺 5.7 億,8 人團隊全員財富自由,最大功臣是 Claude 和 Gemini
Cursor 搭 MCP,一句話就能讓資料庫裸奔!?不是程式碼bug,是MCP 天生架構設計缺陷

相關文章