Signals 在JavaScript中的應用
最近,"Signals"成為了前端備受關注的話題。很多國外的大佬都發文表示Signals是前端框架的未來。同時,尤大也在Vue官網上添加了"Connection to Signals"部分。此外,包括Solid、Angular、Preact、Qwik和Vue等多個前端框架都已經開始實現Signals。
作為一名FE,如果你和我之前一樣還不是很了解Signals,那么這篇文章或許可以幫助你更好地了解一下這個技術。本文將介紹Signals的歷史、概念和優勢。
一、發展歷史
自從聲明式JavaScript框架問世以來,Signals機制一直存在。隨著時間的推移,它采用了許多不同的名稱,經歷了多年的流行和消失。
在聲明式JavaScript框架中,組件是聲明其輸出的單元,可以被動態地渲染和組合。這種方式的優點是,它允許開發人員集中精力于組件的輸出,而無需擔心組件如何被渲染和更新。這種抽象方式也使得組件更容易被復用,并且更容易理解和測試。
然而,這種抽象也帶來了一些挑戰。其中一個挑戰是組件之間如何通信和共享狀態。這些問題可能導致代碼變得笨重,難以維護,并且在復雜的應用程序中容易出現混亂。因此,開發人員需要一種更靈活、更強大的通信機制來解決這些問題。
在這種情況下,Signals機制成為了一個有用的解決方案。Signals機制允許組件在不直接引用其他組件的情況下通信,并且能夠更靈活地傳遞消息和狀態。這些機制可以是事件、回調、Promise或其他異步機制。它們可以被用來處理各種不同的場景,例如用戶交互、網絡請求和狀態更改等。
Signals機制還具有許多其他優點。它們可以提高應用程序的可維護性和可擴展性,并且可以幫助開發人員更好地理解和調試代碼。此外,由于Signals機制允許組件之間松散耦合,因此它們也有助于提高代碼的可重用性。
1.1 早期實現
有時令人驚訝的是,多個團隊幾乎在同一時間達成了相似的解決方案。聲明式JavaScript框架的開端有三個版本:Knockout.js(2010年7月),Backbone.js(2010年10月)和Angular.js(2010年10月)。
Angular的臟檢查,Backbone的模型驅動的重渲染,以及Knockout的細粒度更新。每一個都略有不同,但最終都將成為我們今天管理狀態和更新DOM的基礎。
1.2 數據綁定
Angular.js里面常用的模式叫作數據綁定。數據綁定是將部分狀態(state)附加到視圖樹(view tree)某個特定部分的一個方法。可以做到的一個強大的事情是使其成為雙向的。因此,我們可以讓狀態更新 DOM,反過來,DOM 事件自動更新狀態,所有這些都是以一種簡單的聲明方式進行的。但是如果濫用也會出現問題,在 Angular 中,如果不知道有什么變化,就會對整個樹進行骯臟的檢查,向上傳播可能會導致它發生多次。
1.3 Mobx
之后就是react的時代,react對狀態管理沒有太多的限制。MobX就是這種解決方案。它強調一致性和無障礙傳播。也就是說,對于任何給定的變化,系統的每一部分都只運行一次,而且是以適當的順序同步運行。
它通過將先前方案中典型的基于 push 的響應式換成 push-pull 混合系統來做到這一點。變化的通知被推送出去,但派生狀態的執行被推遲到讀取它的地方。

1.4 Vue
Vue(2014) 也為今天的發展提供了巨大的貢獻。除了在優化一致性方面與 MobX 保持一致外,Vue從一開始就將「細粒度」的響應性作為其核心。
雖然 Vue 與 React 共享虛擬 DOM 的使用,但響應性是一流的,這意味著它首先作為一種內部機制與框架一起開發,以支持其 Options API,并在過去幾年中,成為 Composition API 的核心 (2020)。
Vue 通過調度任務,將 pull / push 機制向前推進了一步。默認情況下,Vue 的修改不會立馬被執行,而是要等到下一個微任務才會執行。
然而,這種調度也可以用來做一些其他的事情,比如 keep-alive,以及 Suspense。甚至像并發渲染這樣的事情也可以用這種方法來實現,真正展示了如何獲得基于 pull 和 push 的兩種方法的最佳效果。
二、為什么是Signals
Signals 的獨特之處在于狀態更改會以最有效的方式來自動更新組件和 UI。Signals 基于自動狀態綁定和依賴跟蹤提供了出色的工效,并具有針對虛擬 DOM 優化的獨特實現。
2.1 狀態管理的困境
隨著應用越來越復雜,項目中的組件也會越來越多,需要管理的狀態也越來越多。
為了實現組件狀態共享,一般需要將狀態提升到組件的共同的祖先組件里面,通過 props 往下傳遞,帶來的問題就是更新時會導致所有子組件跟著更新,需要配合 memo 和 useMemo 來優化性能。
雖然這聽起來還挺合理,但隨著項目代碼的增加,我們很難確定這些優化應該放到哪里。
即使添加了 memoization,也常常因為依賴值不穩定變得無效,由于 Hooks 沒有可以用于分析的顯式依賴關系樹,所以也沒法使用工具來找到原因。

另一種解決方案就是放到 Context 上面,子組件作為消費者自行通過 useContext 來獲取需要的狀態。
但是有一個問題,只有傳給 Provider 的值才能被更新,而且只能作為一個整體來更新,無法做到細粒度的更新。
為了處理這個問題,只能將 Context 進行拆分,業務邏輯又不可避免地會依賴多個 Context,這樣就會出現 Context 套娃現象。

2.2 通向未來的 Signals
Signal 的核心是一個通過.value屬性 來保存值的對象。它有一個重要特征,那就是 Signal 對象的值可以改變,但 Signal 本身始終保持不變。
在 Preact 中,當 Signal 作為 props 或 context 向下傳遞時,傳遞的是對 Signal 的引用。這樣就可以在不重新渲染組件的情況下更新 Signal,因為傳給組件的是 Signal 對象而不是它的值。
這讓我們可以跳過所有昂貴的渲染工作,立即跳到任意訪問 signal.value 屬性的組件。

Signals 具有第二個重要特征,即它們會跟蹤其值何時被訪問以及何時被更新。在 Preact 中,當 Signal 的值發生變化時,從組件內訪問 Signal 的屬性會自動重新渲染組件。
通過Preact的使用,我們可以總結Signals 幾點特點:1、感覺上像是使用原始數據結構 2、能根據值的變化自動更新 3、直接更新 DOM (換句話來說無 VDOM) 4、沒有依賴數組
三、在SolidJS中的使用
可以看到 SolidJS 響應式也是Signal 作為基礎,createSignal 既可以用于組件內,也可以用于組件外,這個跟 Preact 中類似。一方面可以將 Signal 作為組件的 local state,也可以定義為 global State。與前面類似,SolidJS 中也有以下相似點:
- 響應式細粒度更新
- 無需定義 dependencies
- 惰性取值
SolidJS 與 Mobx 和 Vue 的響應式非常相似,但是不會處理 VDOM,而是直接更新 DOM。
四、手動實現一個
響應式狀態管理三要素,Signals、Reactions、Derivations
Signals是一個基礎的數據更新與讀取,Reactions 是可以追蹤訂閱到 Signals 的變化,所以在 Reactions 函數里設置 Derivations 的值。
五、最后
本文是學習Signals的一些記錄。希望能通過介紹響應式狀態管理的一些歷史和理念,讓你對狀態管理有全面的認識,如果感覺本文介紹的不夠詳細,可以閱讀下面的引用原文。
六、引用
- ??https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob??
- ??https://dev.to/this-is-learning/react-vs-signals-10-years-later-3k71??
- ??https://mp.weixin.qq.com/s/Tn0rbkCdFw4f-3ihKUEYQA??
- ??https://indepth.dev/posts/1289/solidjs-reactivity-to-rendering??
- ??https://preactjs.com/guide/v10/signals/??
- ??https://preactjs.com/blog/introducing-signals??


























