Signals 在JavaScript中的應(yīng)用
最近,"Signals"成為了前端備受關(guān)注的話題。很多國外的大佬都發(fā)文表示Signals是前端框架的未來。同時,尤大也在Vue官網(wǎng)上添加了"Connection to Signals"部分。此外,包括Solid、Angular、Preact、Qwik和Vue等多個前端框架都已經(jīng)開始實現(xiàn)Signals。
作為一名FE,如果你和我之前一樣還不是很了解Signals,那么這篇文章或許可以幫助你更好地了解一下這個技術(shù)。本文將介紹Signals的歷史、概念和優(yōu)勢。
一、發(fā)展歷史
自從聲明式JavaScript框架問世以來,Signals機制一直存在。隨著時間的推移,它采用了許多不同的名稱,經(jīng)歷了多年的流行和消失。
在聲明式JavaScript框架中,組件是聲明其輸出的單元,可以被動態(tài)地渲染和組合。這種方式的優(yōu)點是,它允許開發(fā)人員集中精力于組件的輸出,而無需擔(dān)心組件如何被渲染和更新。這種抽象方式也使得組件更容易被復(fù)用,并且更容易理解和測試。
然而,這種抽象也帶來了一些挑戰(zhàn)。其中一個挑戰(zhàn)是組件之間如何通信和共享狀態(tài)。這些問題可能導(dǎo)致代碼變得笨重,難以維護,并且在復(fù)雜的應(yīng)用程序中容易出現(xiàn)混亂。因此,開發(fā)人員需要一種更靈活、更強大的通信機制來解決這些問題。
在這種情況下,Signals機制成為了一個有用的解決方案。Signals機制允許組件在不直接引用其他組件的情況下通信,并且能夠更靈活地傳遞消息和狀態(tài)。這些機制可以是事件、回調(diào)、Promise或其他異步機制。它們可以被用來處理各種不同的場景,例如用戶交互、網(wǎng)絡(luò)請求和狀態(tài)更改等。
Signals機制還具有許多其他優(yōu)點。它們可以提高應(yīng)用程序的可維護性和可擴展性,并且可以幫助開發(fā)人員更好地理解和調(diào)試代碼。此外,由于Signals機制允許組件之間松散耦合,因此它們也有助于提高代碼的可重用性。
1.1 早期實現(xiàn)
有時令人驚訝的是,多個團隊幾乎在同一時間達成了相似的解決方案。聲明式JavaScript框架的開端有三個版本:Knockout.js(2010年7月),Backbone.js(2010年10月)和Angular.js(2010年10月)。
Angular的臟檢查,Backbone的模型驅(qū)動的重渲染,以及Knockout的細粒度更新。每一個都略有不同,但最終都將成為我們今天管理狀態(tài)和更新DOM的基礎(chǔ)。
1.2 數(shù)據(jù)綁定
Angular.js里面常用的模式叫作數(shù)據(jù)綁定。數(shù)據(jù)綁定是將部分狀態(tài)(state)附加到視圖樹(view tree)某個特定部分的一個方法??梢宰龅降囊粋€強大的事情是使其成為雙向的。因此,我們可以讓狀態(tài)更新 DOM,反過來,DOM 事件自動更新狀態(tài),所有這些都是以一種簡單的聲明方式進行的。但是如果濫用也會出現(xiàn)問題,在 Angular 中,如果不知道有什么變化,就會對整個樹進行骯臟的檢查,向上傳播可能會導(dǎo)致它發(fā)生多次。
1.3 Mobx
之后就是react的時代,react對狀態(tài)管理沒有太多的限制。MobX就是這種解決方案。它強調(diào)一致性和無障礙傳播。也就是說,對于任何給定的變化,系統(tǒng)的每一部分都只運行一次,而且是以適當(dāng)?shù)捻樞蛲竭\行。
它通過將先前方案中典型的基于 push 的響應(yīng)式換成 push-pull 混合系統(tǒng)來做到這一點。變化的通知被推送出去,但派生狀態(tài)的執(zhí)行被推遲到讀取它的地方。
1.4 Vue
Vue(2014) 也為今天的發(fā)展提供了巨大的貢獻。除了在優(yōu)化一致性方面與 MobX 保持一致外,Vue從一開始就將「細粒度」的響應(yīng)性作為其核心。
雖然 Vue 與 React 共享虛擬 DOM 的使用,但響應(yīng)性是一流的,這意味著它首先作為一種內(nèi)部機制與框架一起開發(fā),以支持其 Options API,并在過去幾年中,成為 Composition API 的核心 (2020)。
Vue 通過調(diào)度任務(wù),將 pull / push 機制向前推進了一步。默認情況下,Vue 的修改不會立馬被執(zhí)行,而是要等到下一個微任務(wù)才會執(zhí)行。
然而,這種調(diào)度也可以用來做一些其他的事情,比如 keep-alive,以及 Suspense。甚至像并發(fā)渲染這樣的事情也可以用這種方法來實現(xiàn),真正展示了如何獲得基于 pull 和 push 的兩種方法的最佳效果。
二、為什么是Signals
Signals 的獨特之處在于狀態(tài)更改會以最有效的方式來自動更新組件和 UI。Signals 基于自動狀態(tài)綁定和依賴跟蹤提供了出色的工效,并具有針對虛擬 DOM 優(yōu)化的獨特實現(xiàn)。
2.1 狀態(tài)管理的困境
隨著應(yīng)用越來越復(fù)雜,項目中的組件也會越來越多,需要管理的狀態(tài)也越來越多。
為了實現(xiàn)組件狀態(tài)共享,一般需要將狀態(tài)提升到組件的共同的祖先組件里面,通過 props 往下傳遞,帶來的問題就是更新時會導(dǎo)致所有子組件跟著更新,需要配合 memo 和 useMemo 來優(yōu)化性能。
雖然這聽起來還挺合理,但隨著項目代碼的增加,我們很難確定這些優(yōu)化應(yīng)該放到哪里。
即使添加了 memoization,也常常因為依賴值不穩(wěn)定變得無效,由于 Hooks 沒有可以用于分析的顯式依賴關(guān)系樹,所以也沒法使用工具來找到原因。
另一種解決方案就是放到 Context 上面,子組件作為消費者自行通過 useContext 來獲取需要的狀態(tài)。
但是有一個問題,只有傳給 Provider 的值才能被更新,而且只能作為一個整體來更新,無法做到細粒度的更新。
為了處理這個問題,只能將 Context 進行拆分,業(yè)務(wù)邏輯又不可避免地會依賴多個 Context,這樣就會出現(xiàn) Context 套娃現(xiàn)象。
2.2 通向未來的 Signals
Signal 的核心是一個通過.value屬性 來保存值的對象。它有一個重要特征,那就是 Signal 對象的值可以改變,但 Signal 本身始終保持不變。
在 Preact 中,當(dāng) Signal 作為 props 或 context 向下傳遞時,傳遞的是對 Signal 的引用。這樣就可以在不重新渲染組件的情況下更新 Signal,因為傳給組件的是 Signal 對象而不是它的值。
這讓我們可以跳過所有昂貴的渲染工作,立即跳到任意訪問 signal.value 屬性的組件。
Signals 具有第二個重要特征,即它們會跟蹤其值何時被訪問以及何時被更新。在 Preact 中,當(dāng) Signal 的值發(fā)生變化時,從組件內(nèi)訪問 Signal 的屬性會自動重新渲染組件。
通過Preact的使用,我們可以總結(jié)Signals 幾點特點:1、感覺上像是使用原始數(shù)據(jù)結(jié)構(gòu) 2、能根據(jù)值的變化自動更新 3、直接更新 DOM (換句話來說無 VDOM) 4、沒有依賴數(shù)組
三、在SolidJS中的使用
可以看到 SolidJS 響應(yīng)式也是Signal 作為基礎(chǔ),createSignal 既可以用于組件內(nèi),也可以用于組件外,這個跟 Preact 中類似。一方面可以將 Signal 作為組件的 local state,也可以定義為 global State。與前面類似,SolidJS 中也有以下相似點:
- 響應(yīng)式細粒度更新
- 無需定義 dependencies
- 惰性取值
SolidJS 與 Mobx 和 Vue 的響應(yīng)式非常相似,但是不會處理 VDOM,而是直接更新 DOM。
四、手動實現(xiàn)一個
響應(yīng)式狀態(tài)管理三要素,Signals、Reactions、Derivations
Signals是一個基礎(chǔ)的數(shù)據(jù)更新與讀取,Reactions 是可以追蹤訂閱到 Signals 的變化,所以在 Reactions 函數(shù)里設(shè)置 Derivations 的值。
五、最后
本文是學(xué)習(xí)Signals的一些記錄。希望能通過介紹響應(yīng)式狀態(tài)管理的一些歷史和理念,讓你對狀態(tài)管理有全面的認識,如果感覺本文介紹的不夠詳細,可以閱讀下面的引用原文。
六、引用
- ??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??