升級到Svelte5後,我不會再用Svelte開發新專案

OSCHINA
↑點選藍字 關注我們
🔗《2024 中國開源開發者報告》正式釋出
線上閱讀:https://talk.gitee.com/report/china-open-source-2024-annual-report.pdf

在過去幾周裡,我一直忙於處理將一個 Web 應用程序升級到 Svelte 5 所帶來的後果。拋開對框架更新換代和遷移煩惱的抱怨,我在遷移過程中遇到了一些有趣的問題。
到目前為止,我沒有看到很多人報告過相同的問題,所以我覺得自己闡述這些問題可能會有所幫助。
我會盡量不在這篇帖子中抱怨太多,因為我很感激多年來享受的 Svelte 3/4。但我想我不會再選擇 Svelte 來開發任何新的專案了。我希望我在這裡的一些反思對其他人也會有所幫助。
如果您對我在這裡提到的問題的復現感興趣,可以在以下連結找到。
  • 無法將狀態儲存到 indexeddb

    https://github.com/sveltejs/svelte/issues/15327

  • 元件解除安裝導致閉包中的變數未定義

    https://github.com/sveltejs/svelte/issues/15327

對速度的需求

首先,讓我簡要地認可一下 Svelte 團隊所努力的方向。看起來版本 5 的大部分重大變化都是圍繞“深層響應性”(deep reactivity)構建的,這允許更細粒度的響應性,從而帶來更好的效能。這當然很好,Svelte 團隊在效能與開發體驗(DX)的協調方面一直表現出色。
在 Svelte 的早期版本中,實現這一目標的主要方式是透過 Svelte 編譯器。涉及許多輔助技術來提高效能,但擁有一個框架編譯步驟給了 Svelte 團隊很大的靈活性,可以在幕後重新排列事物,而無需讓開發者學習新概念。這就是 Svelte 最初如此獨特的原因。
同時,這也導致了一個比以往更加晦澀難懂的系統框架,使得開發者除錯更復雜的問題變得更加困難。更糟糕的是,編譯器存在缺陷,導致了一些只能透過 “盲猜” 重構問題元件才能修復的錯誤。我個人至少遇到過五六次這樣的情況,這也是我最終轉向 Svelte 5 的原因。
儘管如此,我始終認為這是為了速度和生產力而可以接受的權衡。當然,有時我不得不刪除我的專案,並將其遷移到一個新的倉庫,但這個框架確實是一個使用起來的樂趣。
Svelte 5 更是加大了這種權衡的力度 —— 這是有意義的,因為這正是該框架與眾不同的地方。這次的不同之處在於,抽象/效能的權衡並沒有停留在編譯器領域,而是以兩種重要的方式侵入了執行時:
  • 使用 proxies 來支援 deep reactivit
  • 隱式元件生命週期狀態
這兩個改動不僅提升了效能,還讓開發者的 API 看起來更加整潔。為什麼不喜歡呢?
不幸的是,這兩個特性都是抽象洩漏的典型例子,最終是開發變得更加複雜,而不是更簡單。

Proxies 不是 Objects

使用 proxies 似乎讓 Svelte 團隊能夠在不要求開發者做額外工作的前提下,從框架中榨取更多效能。
在 React 等框架中,透過多個元件層級傳遞狀態而不引發不必要的重新渲染,是一項臭名昭著的困難任務。Svelte 的編譯器避免了與虛擬 DOM 比較解決方案相關的一些陷阱,但顯然仍有足夠的效能提升,足以證明引入 proxies 的合理性。
Svelte 團隊似乎也認為他們的引入代表了開發者體驗的改進:
我們…… 可以最大化兼顧效率和人體工學。
問題是:Svelte 5 看起來 更簡單,但實際上引入了 更多 的抽象。
使用 proxies 來監控陣列方法很有吸引力,因為它允許開發者忘記確保狀態是響應性的所有古怪啟發式方法,只需向陣列中push即可。我無法計算我在 Svelte 4 中寫了多少次value = value來觸發響應性。在 Svelte 4 中,開發者必須瞭解 Svelte 編譯器的工作原理。編譯器作為一個有缺陷的抽象,迫使使用者知道賦值是用來表示響應性的方式。在 Svelte 5 中,開發者可以“忘記”編譯器!
但實際上,他們不能。所有新抽象的引入實際上只是引入了更多複雜的啟發式方法,開發者必須將它們記在心裡,以便讓編譯器按照他們的意願工作。
事實上,這就是為什麼在使用 Svelte 多年後,我發現自己在越來越多地使用 Svelte stores,而響應性宣告則越來越少。原因在於 Svelte stores 就是 JavaScript。在 store 上呼叫update很簡單,而且能夠用$來引用它們只是個額外的便利 —— 無需記住,如果編譯器出錯,它就會提醒我。
proxies 引入了與響應性宣告類似的問題,那就是它們看起來像一件事,但在邊緣上卻表現得像另一件事。當我開始使用 Svelte 5 時,一切執行得都很順利 —— 直到我嘗試將 proxies 儲存到 indexeddb(GitHub 上的 issue),那時我遇到了DataCloneError。更糟糕的是,沒有透過try/catch結構化克隆來可靠地判斷某個物件是否是Proxy,這是一個性能密集型操作。
這迫使開發者記住哪些是 proxies,哪些不是,每次將 proxies 傳遞給一個不期望或不知道它們的上下文時,都要呼叫$state.snapshot。這抵消了他們最初給予我們的所有美好抽象。

元件不是函式

虛擬 DOM 在 2013 年之所以能夠流行起來,是因為它能夠將應用程式建模為一系列組合函式,每個函式接收資料並輸出 HTML。Svelte 保留了這種正規化,使用編譯器來規避虛擬 DOM 的低效和生命週期方法的複雜性。
在 Svelte 5 中,元件生命週期又回來了,採用了 react-hooks 風格。在 React 中,hooks 是一種抽象,它允許開發者避免編寫與元件生命週期方法相關的所有狀態程式碼。現代 React 教程普遍推薦使用 hooks,這些 hooks 依賴於框架在不可見的方式下同步狀態與渲染樹。
雖然這確實會導致程式碼更簡潔,但也要求開發者謹慎行事,以避免破壞圍繞 hooks 的假設。只需嘗試在setTimeout中訪問狀態,你就會明白我的意思。
Svelte 4 有幾個類似的陷阱 —— 例如,與元件的 DOM 元素互動的非同步程式碼必須跟蹤元件是否已解除安裝。這和你在依賴生命週期方法的舊 React 元件中看到的那種模式非常相似。
在我看來,Svelte 5 透過新增與元件生命週期相關的隱式狀態來協調狀態變化和效果,似乎是走上了 React 16 的道路。例如,以下是$effect文件的摘錄:
您可以將 $effect 放置在任何位置,而不僅僅是元件的最頂層,只要它在元件初始化期間(或父級效果啟用時)被呼叫。然後它將與元件(或父級效果)的生命週期相關聯,因此當元件解除安裝(或父級效果被銷燬)時,它將自動銷燬。
這非常複雜!為了有效地使用 $effect…(抱歉),開發者必須理解狀態變化是如何被追蹤的。元件生命週期文件聲稱:
在 Svelte 5 中,元件的生命週期只包含兩個部分:其建立和其銷燬。介於兩者之間的一切 —— 當某些狀態更新時 —— 與元件整體無關;只有需要響應狀態變化的那些部分才會收到通知。這是因為底層最小的變化單位實際上不是元件,而是元件在初始化時設定的(渲染)效果。因此,並沒有 “更新前”/“更新後” 鉤子這樣的東西。
然而,它接著介紹了與$effect.pre結合的 “tick” 概念。本節解釋說,“tick返回一個 promise,它在任何掛起的州變化被應用後解決,或者在下一個微任務中如果沒有掛起的變化時解決。”
我確信有一些心理模型可以證明這一點,但我不認為當必須緊接著關於狀態變化的補充說明時,聲稱元件的生命週期僅由掛載 / 解除安裝組成真的很有幫助。這個地方真正讓我感到困擾,也是這篇部落格帖子的動機所在,那就是當狀態與元件的生命週期耦合在一起時,即使這個狀態被傳遞給一個對 Svelte 一無所知的函式。
在我的應用程式中,我透過在儲存中儲存我想要渲染的元件及其屬性來管理模態對話方塊,並在應用程式的layout.svelte中渲染它。這個儲存也與瀏覽器歷史同步,以便使用後退按鈕關閉它們。有時,向這些模態之一傳遞一個回撥是有用的,將呼叫者特定的功能繫結到子元件上:
const {value} = $props()const callback = () => console.log(value)const openModal = () => pushModal(MyModal, {callback})
這是 JavaScript 中的一個基本模式。傳遞迴調只是你做的事情之一。
不幸的是,如果上述程式碼位於模態對話方塊本身中,呼叫元件會在回撥被呼叫之前被解除安裝。在 Svelte 4 中,這執行得很好,但在 Svelte 5 中,當元件解除安裝時,value會被更新為undefined。這裡有一個最小化複製的例子。
這只是一個例子,但對我來說,很明顯,任何被生命週期比其元件長的回撥函式封閉的屬性,在我想要使用它時都會是undefined—— 沒有任何重新賦值存在於詞法作用域中。
這根本不是 JavaScript 的工作方式。我認為 Svelte 之所以這樣做,是因為它試圖重新發明垃圾回收。因為value是元件的屬性,它顯然需要在元件生命週期的末尾被清理。我確信這背後有很好的工程原因,但這確實令人驚訝。

結論

簡單的事情很美好,但正如 Rich Hickey 所說,簡單的事情並不總是簡單的。而且像 Joel Spolsky 一樣,我不喜歡感到意外。Svelte 一直充滿了魔法,但在我看來,隨著最新版本的釋出,重複咒語的認知成本終於超過了它賦予的力量。
在這篇文章中,我的目的並不是貶低 Svelte 團隊。我知道很多人喜歡 Svelte 5(以及 react hooks)。我試圖表達的觀點是,在為使用者做事和賦予使用者自主權之間有一個權衡。好的軟體是建立在理解之上,而不是聰明之上
我也認為,隨著 AI 輔助編碼越來越受歡迎,記住這一點非常重要。不要選擇讓你與工作疏遠的工具。選擇那些利用你已經積累的智慧,並幫助你深化對這門學科理解的工具。感謝 Rich Harris 及其團隊多年來愉快的開發經歷。我希望(如果你看到這段話的話),其中的不準確之處不至於影響作為使用者反饋的價值。
相關閱讀
2024前端現狀:開發者最愛用React、最想學習Svelte
今年最火開源前端框架——Svelte 5正式釋出穩定版、徹底重寫、新增$語法 、star數近8萬
相關來源:https://hodlbod.npub.pro/post/1739830562159/
END
熱門文章
分享在看點贊~Orz

相關文章