JavaScript 中 Signals 的演進

高級前端進階 發佈 2024-01-10T03:53:01.576163+00:00

大家好,很高興又見面了,我是"高級前端‬進階‬",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點讚、收藏、轉發!高級前端‬進階前言最近,「 Signals 」一詞在前端世界中引起了不小的討論。

家好,很高興又見面了,我是"高級前端‬進階‬",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點讚、收藏、轉發!

前言

最近,「 Signals 」一詞在前端世界中引起了不小的討論。 在看似很短的時間內,它們好像在很多前端框架中都有出現,從 PReact 到 Angular

但它並不是一個新事物。我們可以追溯到20世紀60年代末的研究。在其基礎上,使第一個電子表格和硬體描述語言(如 Verilog 和 VHDL )得以實現的相同建模。

甚至在 JavaScript 中,自從聲明式 JavaScript 框架誕生以來,我們就擁有了 Signal。隨著時間的推移,它們已經有了不同的名字,並在這些年裡不斷地流行起來。但現在我們又來到了這裡,現在是一個很好的時機,讓我們對其產生的原因以及如何使用有更多的了解。

開端

有時我們會驚訝地發現,多個團隊在完全相同的時間內達成了類似的解決方案。在聲明式 JavaScript 框架的起步階段,有 3 個方案在 3 個月內相繼發布。Knockout.js(2010年7月)、Backbone.js(2010年10月)、Angular.js(2010年10月)。

Angular 的「髒值檢查」,Backbone 的 「模型驅動重複渲染」,Knockout 的「細粒度更新」。每個方案都有些許不同,但最終都成為了今天我們更新 state 和管理 DOM 的基礎。

Knockout.js 對本文的主題特別重要,因為它的「細粒度更新」是建立在我們稱之為 Signals 的基礎上的。他們最初引入了兩個概念:observable(狀態)和 computed(副作用),並且在接下來的幾年裡在業界引入第三個概念 pureComputed(衍生狀態)。

const count = ko.observable(0);

const doubleCount = ko.pureComputed(() => count() * 2);

// doubleCount 更新時執行 console.log
ko.computed(() => console.log(doubleCount()))

狂野大西部

這些模式是在服務端 MVC 開發和過去幾年的 jQuery 中學到的混合模式。其中一個特別常見的模式叫做數據綁定,Angular.js 和 Knockout.js 都有,儘管實現方式略有不同。

數據綁定 是將部分狀態(state)附加到視圖樹(view tree)某個特定部分的一個方法。可以做到的一個強大的事情是使其成為雙向的。因此,我們可以讓狀態更新 DOM,反過來,DOM 事件自動更新狀態,所有這些都是以一種簡單的聲明方式進行的。

然而,如果濫用這種能力,最終會搬起石頭砸自己的腳。在 Angular 中,如果不知道有什麼變化,就會對整個樹進行骯髒的檢查,向上傳播可能會導致它發生多次。在 Knockout 中,由於你在樹上來回走動,所以很難跟蹤變化的路徑,循環是很常見的。

無障礙

隨之而來的是 React 的大規模採用。一些人仍然喜歡響應式模型,而且由於 React 對狀態管理沒有太多的限制,所以很有可能將兩者混合起來。

Mobservable(2015)就是這種解決方案。但比起與 React 合作,它帶來了新的東西。它強調一致性和無障礙傳播。也就是說,對於任何給定的變化,系統的每一部分都只運行一次,而且是以適當的順序同步運行。

它通過將先前方案中典型的基於 push 的響應式換成 push-pull 混合系統來做到這一點。變化的通知被推送出去,但派生狀態的執行被推遲到讀取它的地方。

雖然這個細節在很大程度上被一個事實所掩蓋,即無論如何 React 都會重新渲染讀取變化的組件,但這為這些系統可調試性和一致性方面的提升邁出了重要一步。在接下來的幾年裡,隨著算法的不斷完善,我們會看到一個趨勢在不斷的完善。

征服泄漏的觀察者

細粒度的響應性是 觀察者模式 的一個變種。雖然這是一個強大的同步模式,但它也有一個典型的問題。一個 Signal 會保留對其訂閱者的強引用,所以一個長時間存在的 Signal 會保留所有的訂閱,除非是手動處理掉。

這種方式在大量使用時變得非常複雜,尤其是涉及嵌套的時候。在處理分支邏輯和樹時,嵌套是很常見的,就像你在構建用戶界面視圖那樣。

一個不太知名的庫,S.js(2013),提供了答案。S.js 是獨立於其他大多數解決方案而開發的,它更直接地以數字電路為模型,所有的狀態變化都在時鐘周期內進行。它將其狀態基元稱為 "Signals(信號)"。雖然不是第一次使用這個名字,但它是我們今天使用的術語的來源。

更重要的是,它引入了響應式所有權的概念。一個所有者將收集所有的子響應式作用域,並在所有者自己的 deposal 邏輯或在重新執行時管理它們的 deposal 邏輯。響應式視圖將從一個根所有者開始,然後每個節點將作為其後代的所有者。這種所有者模式不僅對 deposal 很有用,而且是在響應式視圖中建立 Provider / Consumer 上下文的一種機制。

調度

Vue (2014) 也為今天的發展提供了巨大的貢獻。除了在優化一致性方面與 MobX 保持一致外,Vue從一開始就將「細粒度」的響應性作為其核心。

雖然 Vue 與 React 共享虛擬 DOM 的使用,但響應性是一流的,這意味著它首先作為一種內部機制與框架一起開發,以支持其 Options API,並在過去幾年中,成為 Composition API 的核心 (2020)。

Vue 通過調度任務,將 pull / push 機制向前推進了一步。默認情況下,Vue 的修改不會立馬被執行,而是要等到下一個微任務才會執行。

然而,這種調度也可以用來做一些其他的事情,比如 keep-alive(在沒有計算成本的情況下保留屏幕外的圖形),以及 Suspense。甚至像 並發渲染 這樣的事情也可以用這種方法來實現,真正展示了如何獲得基於 pull 和 push 的兩種方法的最佳效果。

編譯

2019年,Svelte 3 向大家展示了我們可以用一個編譯器做多少事情。事實上,他們把響應性完全編譯掉了。這並非沒有取捨,但更有趣的是,Svelte 向我們展示了一個編譯器如何能撫平人體工程學的缺點。而這將繼續成為前端的一個趨勢。

響應性語言的特性:狀態、派生狀態和副作用;不僅為我們提供了描述用戶界面等同步系統所需的一切,而且是可分析的。我們可以準確地知道什麼地方發生了什麼變化。可追溯性的潛力是深遠的。

如果我們在編譯時知道這一點,我們就可以少發一些 JavaScript 。我們可以在代碼加載方面更加自由。這就是 Qwik 和 Marko 的可恢復性的基礎。

通往未來的 Signals

Signals 是新的 VDOM。

人們的興趣大增:許多人正在嘗試一些新的東西。這將讓我們探索這個空間,嘗試不同的策略,理解和優化。

不知道我們最終會確定什麼,但這種集體探索是很好的! —— Pawel Kozlowski

鑑於這項技術有多老,說還有很多東西需要探索,可能會讓人驚訝。這是因為它是一種解決方案的建模方式,而不是一種特定的技術。它所提供的是一種描述狀態同步的語言,與你要讓它執行的任何副作用無關。

那麼,它被 Vue、Solid、Preact、Qwik 和 Angular 採用似乎也就不足為奇了。我們已經看到它進入了 Rust 的 Leptos 和 Sycamore,表明 DOM 上的 WASM 不一定很慢。它甚至被 React 考慮在引擎使用。

我們可能會在 React 中添加一個類似信號的基元,但我不認為這是編寫 UI 代碼的一個好方法。它對性能來說是很好的。但我更喜歡 React 的模式,在那裡你假裝每次都會重新創建整個東西。我們的計劃是使用一個編譯器來實現相當不錯的性能提升。—— React 團隊核心成員 Andrew Clark

也許這很合適,因為 React 的 虛擬DOM 始終只是一個實現細節。

Signals 和響應性語言似乎是事情的交匯點。但這在其第一次進入 JavaScript 的時候並不那麼明顯。也許這是因為 JavaScript 並不是最適合它的語言。

無論這一切的結局是什麼,到目前為止都是一次相當不錯的旅程。有這麼多人關注 Signals,我迫不及待地想知道我們的下一步會是什麼。

總結

以上簡單介紹了 Javascript 中 Signals 的演變,希望對正在學習前端的你有所幫助。當然,這並不是所有的內容,後續我還會一直更新這篇文章,從更多方面去探討前端中的 Signals。最後感謝大家對本文的支持~歡迎點讚收藏,在評論區留下你的高見

參考資料

原文作者:Ryan Carniato

原文連結:https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob

關鍵字: