React不是真正的響應式編程,Svelte才是
作者 | Ovie Okeh
譯者 | 王強
這個題目可能有點誇張,但不管怎樣 Svelte 和它的理念就是這樣的。如果你還沒聽說過 Svelte 的話就去了解一下吧——你會見證一場革命的,它將取得空前的成就(沒有給 Svelte 團隊增加壓力的意思)。
本文不是 Svelte 的入門教程。Svelte 團隊已經做了一份很棒的互動式手把手入門教程,能幫助你輕鬆邁入響應式編程的天地。
Svelte 教程地址:https://svelte.dev/tutorial/basics
先來份免責聲明吧: 我不是編程高手,我也沒有無所不知。我只是非常熱衷於每天出現的創新事物,喜歡隨時隨地談論它們而已——這也是這篇文章的來歷。請客觀對待我說的每一句話,如果我哪裡跑了火車也請告訴我吧。
那好,我們這就開始正題吧!
首先,談談 React
討論為什麼我認為 Svelte 有如此革命性的突破之前,咱們先來看看之前 Dan 發布的「React並不是完全響應式的」這條推文,研究一下它背後的含義:
另一個免責聲明:本文可沒有批判 React 的意思。我只是拿 React 來舉個例子說明問題,因為大多數讀者遲早都會用到它的。提 React 只是因為它是跟 Svelte 對比的最佳例子罷了。
Dan 上面那句話究竟是什麼意思?這對我們現在寫代碼的方式有什麼影響?要回答這個問題,我先簡單介紹一下 React 的工作機制吧。
當你呈現一個 React 應用時,React 會在所謂虛擬 DOM 中保留 DOM 的副本。虛擬 DOM 充當你的 React 代碼與瀏覽器繪製到 DOM 的內容之間的中間層。
然後當你的數據出現變動時(可能因為你調用了 this.setState,useState),React 會做一些工作來確定如何在屏幕上重新繪製 UI。
它會對比虛擬 DOM 與真實的 DOM,以確定數據更新導致了哪些更改。然後它會僅重新繪製與虛擬 DOM 中的新副本不匹配的 DOM 部分,這樣就無需在每次數據更新時重新繪製整個 DOM 了。
這就顯著提升了性能,因為更新虛擬 DOM 比更新真實 DOM 要節省很多資源,而 React 只更新真實 DOM 中需要改變的部分。有一篇文章很好地解釋了這一過程:
https://medium.com/@gethylgeorge/how-virtual-dom-and-diffing-works-in-react-6fc805f9f84e
但你可能會發現這個實現有點問題。如果你沒有告訴 React 你的數據已經改變了(比如說調用 this.setState 或 Hooks 之類),那麼虛擬 DOM 就不會有變化,React 也不會隨之響應(Duang!搞砸了!)。
這就是 Dan 所說的,React 並不是完全的響應式設計的意思。React 需要你手動跟蹤應用數據,並在數據變化時告訴 React,這也意味著你得做更多工作。
好了,現在該談 Svelte 了
Svelte 是一種構建 UI 的全新途徑,它速度極快、效率極高,是真正的響應式設計,還不需要虛擬 DOM;用 Svelte 寫的代碼比其它任何框架或庫都更加簡潔。
說得這麼好聽,可你肯定會問它和其它一大堆 JavaScript 庫和框架究竟有什麼區別呢?我來逐一說明吧,
真正的響應式設計
Svelte 既不是庫也不是框架;相反,Svelte 是一個編譯器,它吃進你的代碼並吐出與你的 DOM 直接交互的原生 JavaScript,不需要中間層。
等等,什麼?編譯器?是的——編譯器。這個思路太強悍了,我都不知道為什麼以前沒人想得到呢?為什麼這個主意這麼棒,聽我細細道來吧。
引一句 Rich Harris 在 YGLF 2019 大會上的講話:
Svelte 3.0 將響應設計從組件 API 移到了編程語言中。
這說的是啥?別急,我們已經看到 React 和大多數其他前端框架,要求你在更新其虛擬 DOM 之前,使用 API 來告訴它數據已更改(再次通過調用 this.setState 或 useState)。
在 React 以及大多數 UI 框架和庫中,調用 this.setState 意味著你的應用的響應能力是與特定的 API 綁定的,沒有 API 它就沒法知道數據什麼時候變動了。
Svelte 採取了另一種方法解決這個問題。
它從運行代碼的方式中獲取了 Observable 的靈感。 它不是從上到下運行代碼,而是以拓撲順序運行它。 查看下面的代碼片段,我們將了解以拓撲順序運行它的含義。
現在如果你按從上到下的順序運行這幾行代碼的話就會在第 4 行遇到錯誤,因為 secondNumber 依賴 firstNumber,而這時候 firstNumber 尚未初始化。
如果以拓撲順序運行這段代碼則不會出現任何錯誤。為啥呢?編譯器並不會按從上到下的順序運行這段代碼;相反,它會查看所有變數並生成依賴圖(比如說 A 依賴 B 才能工作之類)。
這算是對編譯器如何以拓撲順序編譯代碼的簡化解釋了。
乍看上去代碼好像是從上到下的運行順序,但仔細觀察就會發現它的確是跳著執行的。
跑到第 4 行時,編譯器發現它沒有 firstNumber,因此會暫停執行並查看代碼,找出它是不是在別的地方定義了。一看,原來它是在第 5 行定義的,所以編譯器會先運行第 5 行,然後返回第 4 行繼續執行。
如果語句 A 依賴於語句 B,則語句 B 會先運行,運行順序與聲明的順序無關。
那麼這和 Svelte 實現真正的響應式設計又有什麼關係?具體來說,你可以在 JavaScript 中用標識符標記一個語句,如下所示:$: foo = bar。它會在 foo = bar 語句中添加一個名為 $ 的標識符(如果之前未定義 foo,則嚴格模式下會出錯)。
所以在這種情況下,當 Svelte 看到任何帶有 $: 前綴的語句時,它就知道左邊的變數要從右邊的變數中獲取值。我們現在有了一種方法可以將一個變數的值綁定到另一個變數。
響應!這意味著我們現在正在使用 JavaScript 的 API 核心部分來實現真正的響應設計,無需擺弄像 this.setState 這樣的第三方 API。
實踐中是這個樣子:
請注意,在上面的代碼中我們不需要將 bar 重新分配給 foo 的新值——比如直接通過 bar = foo 10;或者通過調用像 this.setState({ bar = foo 10 }); 這樣的 API 方法,現在都用不著了。它會自動為我們處理好的。
這意味著當你將 foo 更改為等於 15 時,bar 會自動更新為 25,並且你不必調用 API 來為你更新它。Svelte 已經知道了。
上面的 Svelte 代碼的編譯版本如下所示:
好好花點時間研究一下上面這段代碼吧,慢慢來,不要著急。
看到在 bar 被定義之前 foo 是如何更新的了嗎? 那是因為編譯器正在以拓撲順序,而非嚴格的自上而下的順序在解析 Svelte 代碼。
Svelte 會自己響應數據變化。它用不著你操心更改的內容和時間;它自己就會知道。
注意:在第 4 行里,bar 的值到下一個 Event Loop 之前都不會更新的,這樣一切都會幹凈又整潔。
這樣你就不必在數據發生變化時手動更新狀態了。你可以專註於你的代碼邏輯,而 Svelte 可以幫助你將 UI 與最新狀態協調好。
簡潔
前面我不是說 Svelte 可以用更少的代碼來完成更多工作嗎?事實確實如此。下面我拿 React 中一個簡單的組件和 Svelte 中的對應組件舉個例子,你自己看:
17 行對 29 行代碼,這倆應用的功能完全相同,看看我們在 React.js 中編寫了很多的代碼吧——這我還沒開始用 Angular 呢。
Svelte 代碼除了更簡潔耐看外也更容易理解,因為它的活動部件比 React 代碼少。我們不需要事件處理程序來更新輸入元素的值——只需綁定值即可。
回想你剛剛開始學習網頁開發的時候。哪邊的代碼會讓你更難理解?左邊的還是右邊的?
雖然這看起來沒那麼重要,但當你開始構建更大、更複雜的應用時,很快就會發現不用寫那麼多代碼是多麼有用。我曾花了好幾個小時試圖理解同事編寫的大型 React 組件是如何工作的。
我確實相信 Svelte 的簡化 API 能使我們更快地閱讀和理解代碼,從而提高整體工作效率。
性能
好了,現在我們已經知道 Svelte 是真正的響應式設計,可以讓你用更少的投入做更多的事情。那麼它的性能如何?完全用 Svelte 編寫的應用能有很好的用戶體驗嗎?
React 之所以如此強大,其原因之一在於它使用虛擬 DOM 來更新應用程序的 UI,一次只更新一部分,無需在每次更改內容時重新構建整個 DOM(這非常消耗資源)。
但這種方法的缺點是,如果組件的數據發生變化,React 將重新渲染該組件及其所有子組件,哪怕子組件不需要重新渲染也得這麼干。這就是為什麼 React 會有 shouldComponentUpdate、useMemo、React.PureComponent 一類的 API。
只要使用虛擬 DOM 在狀態更改時渲染 UI,這個問題就沒法解決。
Svelte 不使用虛擬 DOM,那麼它如何解決重新繪製 DOM 以匹配應用程序狀態的問題呢?這裡我再次引用 Rich Harris 在 YGLF 上的精彩演講:
框架不是用於組織代碼的工具。它們是組織你思想的工具。
Rich 認為框架可以在構建步驟中運行,從而讓代碼在運行時無需中間層。這也就是為什麼 Svelte 是編譯器而非框架的原因。
這就是為什麼 Svelte 速度飛快的原因。Svelte 將你的代碼編譯為一個直接與 DOM 交互的高效底層代碼。但 Svelte 是如何解決數據更改時重新繪製整個 DOM 的問題呢?
像 React 這樣的框架需要你調用 API 方法,在數據發生變化時告訴它;但使用 Svelte 時,只需使用賦值運算符 = 就足夠了。
如果狀態變數——比如說 foo ——使用 = 運算符更新,則 Svelte 將僅更新依賴 foo 的其它變數,如前所示。這讓 Svelte 可以僅繪製 DOM 的一部分內容,這些部分以某種方式從 foo 中獲取它們的值。
我將省略實際的實現方式,因為這篇文章已經足夠長了。你可以看看 Rich Harris 自己的解釋:
https://www.youtube.com/watch?v=AdNJ3fydeao
結 語
Svelte 3.0 是最近軟體開發業的福音之一。有些人可能會說這是誇大其詞,但我不這麼認為。Svelte 背後的理念及其實現將使我們能向瀏覽器發送更少的 JS 模版,卻做更多的事情。
反過來,這會帶來性能更強、更輕量的應用程序,並生成更易閱讀的代碼。那麼現在,Svelte 將很快取代 React、Angular 或其它流行前端框架嗎?
現在我可以說答案是否定的。與它們相比 Svelte 相對年輕,所以它需要時間來成長、成熟,並解決一些我們可能還沒發現的問題。
就像 React 誕生後改變了軟體開發產業一樣,Svelte 也有可能改變我們對框架的看法,以及我們開發新事物時的思路。
英文原文
https://blog.logrocket.com/truly-reactive-programming-with-svelte-3-0-321Ωb49b75969
點個在看少個 bug
※AI與IoT之間差的是數據?揭秘Keep快速入局AIoT的三大絕招
※熱度3年猛增20倍,Serverless&雲開發的技術架構全解析
TAG:InfoQ |