7 月 22 日上午觀看了 第二屆稀土開發者大會 尤大關於 《2022 Web 前端生態趨勢》 的主題分享,記錄如下,有時間的同學可以觀看大會的回放:https://juejin.cn/live/xdc202201
先放一張整理好的大綱:


在過去幾年中,發生的最大的一個開發範式層面的一個變化肯定是 React Hooks,React Hooks 推出可以說是啟發了很多組件邏輯表達和邏輯附用的新範式。
在 React 生態中,現在已經幾乎徹底取代了 Class Components,在新的 React 項目中,大家已經很少看到採用 Class Components 的這些項目了。
在其他框架中,React Hooks 也產生了很大的影響。比如說 Vue 受到 Hooks 的影響推出了 Composition API,也就是組合式 API。而 Svelte 受到 Hooks 的影響推出了 Svelte 3。其實 Svelte 3 的整個的組件 編譯的這個邏輯是由 React Hooks 啟發而來的。
在 React 生態之外啊也有一些跟 React 更接近但是卻不在 React 體系內的後起之秀。比如說像 SolidJS,它其實語法上跟 Hooks 語法更相似,但是實現上卻跟 Vue Composition API 更相似的一個內在的實現。
所以說 React Hooks 還是啟發了很多新的範式。

雖然有這樣的潮流的影響力在,但是隨着 React Hooks 被大量的廣泛使用,它的開發中的一些體驗問題也逐漸的被大家所證實。這是一些不可迴避的開發體驗的問題。這裡面根本的原因是在於 React Hooks 的執行原理和原生的 JS 心智模型上的一個差異,因為 React Hooks 是通過把組件的代碼每一次更新都進行重複調用來模擬一些行為,那麼這就導致了一些反直覺的一些限制:

很顯然,作為一個競爭框架的作者,我會對 React Hooks 批評會更加直接一點,但是這些問題其實也並不是我一個人的看法,而是近年來 React 社區,甚至是 React 團隊也已經意識到了一些問題。那麼 React 團隊在這些意識到這些問題後,其實也有在做改善開發體驗的努力:

那麼在這些探索或者說這些改進落地之前,很多 React 的社區成員以及包含一些本身就不使用 React 的用戶,雖然React Hooks 產生了重大的範式影響,但是大家也意識到了它的一些問題。所以反而是跟 React Hooks 有相似的邏輯組合能力、但同時另一方面是基於依賴追蹤的這些範式開始重新得到了重視。

舉個例子來說,其實在 React 社區內部,也有像 Recoil 和 Jotai 這樣的基於依賴追蹤的一些狀態方案。那麼在社區之外就有更多了,比如說 SolidJS、Vue 的組合式 API。甚至老牌框架 Ember 最近也剛剛開源了一個新的項目叫做 Starbeam,是把 Ember 內部的狀態的追蹤管理的這個方案抽象出來。
Solid
與 React Hooks 語法非常相似,副作用 createEffect 和 React Hooks 的 useEffect 類似,但是並不需要手動取聲明依賴,其實在調用 count 這個函數的時候,它就默認自動幫你收集了依賴。
那狀態更新的時候,我們並不需要去用 useCallback,useEvent 這樣的這額外的方式去創造一個一個函數來傳給我們的事件監聽器,這些都是非常符合直覺的。
Vue Composition API
那同樣 Vue Composition API和 Solid 其實本質上內部實現幾乎是一樣的。只是 Solid 看上去更像 React,而 Vue 是更多的用這個 ref 對象,ref 對象上的這個 value 既可以用來讀也可以用來寫,在讀和寫之中就會自動的追蹤和更新依賴。
Ember Starbeam
而 Ember Starbeam,其實也可以看到它的這個 Cell 這個 API,其實就跟 Vue ref API 幾乎是一樣的。那麼它的上面暴露了一個 current 來代表當前的這個值,以及暴露了一個 set 方法來行使狀態更新。
基於依賴追蹤的範式-共同點
不需要思考當重複調用時候會怎麼樣。不需要去思考把這個狀態依賴哪些其他狀態去一個一個列出來,這樣對於整個的這個心智負擔會小一些。無需使用 useCallback,useEvent 這樣的優化手段。
在 React 生態中的 Recoil 或者是 Jotai 這樣的方案雖然也提供了自動的依賴追蹤,和一定程度的組件的更新優化,但是因為它們仍然是需要在 React Hooks 的這個大的體系中使用的。所以呢在很多其他的方面依然會受制於 Hooks 的問題,那麼 Hooks 本身在這些方案之外還是會存在這些過期閉包等等問題。
基於編譯的響應式系統
之前我們也說到 React Forget是一個基於編譯時的優化去改善開發體驗的一個手段。即使是基於依賴追蹤的方案 也可以進行一些基於編譯時的優化。
Svelte
可以看到這個 let 聲明一個變量就是一個響應式的狀態了,你要更新這個狀態就直接去操作這個變量就可以。然後呢副作用是用一個神奇的編譯式的魔法,也就是 $: 語法,使用 $: label 聲明副作用,當 count 發生變化,這個語句就會重新執行。使用編譯的手段讓編碼更簡潔。
Svelte 簡潔的代價

Vue 在 3.2 引入實驗性功能 Vue Reactivity Transform,也就是響應式轉換。可以看到還是一個簡單的聲明,使用一個 $ref 函數,這個函數其實是編譯時的一個類似於宏的這樣一個概念。這個函數並不是真實存在,只是給給編譯器一個提示,那編譯器通過編譯之後,就會把它轉化成我們之前看到的 這個基於真實的 ref 代碼。
但是在使用時候體驗就變成了只是聲明一個變量,然後使用這個變量和更新這個變量就跟使用一個普通的 JavaScript 變量沒有區別了。同時呢這個語法因為我們在聲明的時候會顯式聲明說哪個變量是響應式的 哪個變量不是響應式的,所以這個語法其實可以在嵌套的函數中使用,也可以甚至在普通的 js、ts 文件中使用,它並不限制於 Vue 文件,所以這是一個比較更普適的基於編譯的一個響應式模型。
Solid-labels
那麼在 Solid 生態中,其實也有受啟發於 Vue Reactivity Transform,它的社區用戶做的一個 Solid-labels,也是基於 Solid 的響應式方案。然後再做一層編譯時的優化,那麼可以看到跟 Vue Reactivity Transform 能夠達成的效果非常相似。那最終的目的都是相似的,就是讓大家可以用更簡潔的代碼去表達這個組件邏輯,同時又不放棄像 React Hooks 那樣進行自由的邏輯組合的這些能力。所以說這也是一個很有意思的探索方向。
統一模型的優勢和代價
和 Svelte 相比,Vue Reactivity Transform 和 Solid-labels 都是我所謂的統一模型。那也就是說它不受限於組件上下文,它可以在組件內使用也可以在組件外使用。它的優勢就是有利於長期的重構和復用。為什麼呢,因為很多時候我們的大型項目中的邏輯復用都是在我們一個組件,寫着寫着發現這個組件變得很臃腫,我們才開始考慮要把邏輯開始重新組織、抽取復用。那麼 Svelte 由於他的語法只能在組件內使用,這就使得把邏輯挪到組件外成為一個 代價相當大的行為。並不是一個簡單的說把這部分邏輯複製出去,而是需要進行一次徹底的重構。因為組件外用的是完全一套不同的系統。但是像用,Vue Reactivity Transform 和 Solid-labels 這樣的方案,我們就可以把組件內的這些邏輯原封不動的直接拷貝到組件外,然後把它包在一個函數裡面抽取就完成了,這樣重構的代價非常小,也就更鼓勵團隊去進行更多的這樣的重構和優化,對於長期的可維護性會有更好的幫助。
當然他的代價就是說因為需要顯式的去聲明響應式的這個變量,所以會有一定程度的底層實現的抽象泄露。也就是說用戶其實是需要先了解底層的響應式模型的實現,然後才能更好的理解這個語法糖是如何運作的。而不像 Svelte 組件中的這個語法呢,即使你完全不了解它底層如何運作,你也可以幾乎可以 0 成本的上手。那麼這就是一個長期的可維護性和一個初期的上手成本之間的一個平衡和和取捨。
基於編譯的運行時優化

Svelte 代碼生成策略相對更繁瑣一些。而 Solid 是基於先生成一個基本的 HTML 字符串,然後在裡面找到對應的 DOM 節點進行綁定。Svelte 是通過生成命令式的一個一個節點,然後把節點拼接這些 Javascript 代碼。那這個策略就導致同等的這個組件源碼之下 Svelte 每個組件的編譯輸出會更臃腫。雖然大家會第一印象是覺得說 Svelte 是以輕量而出名的,但其實我們會發現,在相對大型的項目中,在項目中組件超過 15 個之後,Svelte 的整體的打包體積優勢就已經幾乎不存在了。那麼當組件超過 50 個,甚至是達到 100 個的時候,Svelte 的體積會越來越臃腫。而相對於而言,我們可以看到 Vue 和 Solid 的編譯,整體的這個曲線就平緩很多。所以其實在越大型的項目中反而是 Svelte 的體積優勢反而是一個劣勢。據我所知 Svelte 團隊也有在想要優化這一方面的想法,那麼可能會在下一個大版本中才能實現,我們也會拭目以待。
第三方性能測評(7月):

Vue 在追趕 Solid,Vue Vapor Mode 實驗項目:


整體的思路就是一次性事先生成這個模板的靜態結構,一次性生成這些靜態節點,然後再去生成命令式的找到動態節點並對把它跟狀態進行響應式的綁定的這樣一些代碼。這個策略也是就是幾乎本質上就是 Solid 所採用的策略,那麼其實這個策略可以被所有的模板引擎所使用。所以我們也在探索在之後某個版本的 Vue 當中會引入一個可選的一個模式,把模板編譯成這樣的性能更優的,運行時體積也更小的一個模式。當然這不會是一個破壞性更新,因為我們的目標是可以讓你漸進式的去使用這個功能。
工具鏈





一個語言,前後「打通」
數據的前後端打通



關於作者和聲明


文章轉載於公眾號:Frontend Radio