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