CSS背景圖和HTML的<img>標(biāo)簽怎么選?
Signal(信號)是一種存儲應(yīng)用狀態(tài)的形式,類似于 React 中的 useState()。但是,有一些關(guān)鍵性差異使 Signal 更具優(yōu)勢。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。
Signal 并不是最近才出現(xiàn)的,在此之前,它已經(jīng)存在于 Knockout 等框架中。不過,在最近幾年通過巧妙的編譯器技巧和與 JSX 的深度集成極大地改進了它的開發(fā)者體驗·,這使得它非常簡潔并且使用起來很方便。
下面就來看看 Signal 都有哪些優(yōu)勢,為什么說它是前端框架的未來!
Signal 是什么?
Signal 和 State 之間的主要區(qū)別在于 Signal 返回一個 getter 和一個 setter,而非響應(yīng)式系統(tǒng)返回一個值和一個 setter。
注意: 有些響應(yīng)式系統(tǒng)同時返回一個 getter/setter,有些則返回兩個單獨的引用,但思想是一樣的。
Signal 的優(yōu)勢
State 混淆了兩個獨立的概念:
- StateReference:對狀態(tài)的引用。
- StateValue:存儲在狀態(tài)引用/存儲中的實際值。
那為什么返回一個 getter 比返回一個值更好呢?因為通過返回 getter,可以將狀態(tài)引用的傳遞與狀態(tài)值的讀取分開。
下面以這段 SolidJS 代碼為例:
- createSignal():分配 StateStorage 并將其初始化為 0。
- getCount:可以傳遞的對狀態(tài)的引用。
- getCount():獲取狀態(tài)值。
上面解釋了 Signal 和普通 State 的不同。那 Signal 到底有什么優(yōu)勢呢?Signal 是響應(yīng)式的,這意味著它需要跟蹤誰對狀態(tài)感興趣(訂閱者),如果狀態(tài)發(fā)生變化,則通知訂閱者狀態(tài)變化。
為了具有響應(yīng)式,Signal 必須要收集誰對它的值感興趣。它通過觀察在什么情況下調(diào)用狀態(tài) getter 來獲取此信息。通過從 getter 中獲取值,告訴 Signal 該位置對該值感興趣。如果值發(fā)生變化,則需要調(diào)用 getter 創(chuàng)建一個訂閱。
這就是為什么傳遞狀態(tài) getter 而不是狀態(tài)值很重要的原因。狀態(tài)值的傳遞不會向 Signal 提供有關(guān)實際使用該值的位置的任何信息。這就是為什么區(qū)分狀態(tài)引用和狀態(tài)值在 Signal 中如此重要。
為了進行對比,這里是 Qwik 中的相同示例。請注意,(getter/setter) 已被替換為具有 .value 屬性(表示 getter/setter)的單個對象。雖然語法不同,但內(nèi)部的工作原理是一樣的。
當(dāng)單擊按鈕并增加值時,框架只需要將文本節(jié)點從 0 更新為 1。它可以這樣做是因為在模板的初始渲染期間,Signal 已經(jīng)知道 count.value? 只能被文本節(jié)點訪問。因此,它知道如果 count 的值發(fā)生變化,就只需要更新文本節(jié)點而無需更新其他地方。
useState() 的缺點
下面來看看在 React 中是如何使用 useState() 的以及它的缺點。
React 的 useState()? 會返回一個狀態(tài)值。這意味著 useState()? 不知道組件或應(yīng)用內(nèi)部如何使用狀態(tài)值。所以,一旦通過調(diào)用 setCount() 通知 React 狀態(tài)更改,React 就不知道頁面的哪一部分發(fā)生了更改,因此必須重新渲染整個組件,這在計算上是很昂貴的。
useRef() 不渲染
React 的 useRef()? 類似于 useSignal()?,但它不會導(dǎo)致頁面重新渲染。下面的例子看起來與 useSignal() 非常相似,但它不起作用。
useRef()? 的使用與 useSignal()? 完全一樣,用于傳遞對狀態(tài)的引用而不是狀態(tài)本身。但是,useRef() 缺少了訂閱跟蹤和通知。
在基于 Signal 的框架中,useSignal()? 和 useRef()? 是一樣的。useSignal()? 可以執(zhí)行 useRef() 加上訂閱跟蹤的功能,這樣就進一步簡化了框架的 API。
內(nèi)置的 useMemo()
Signal 很小需要進行記憶,因為它需要做的工作很少。
下面來看一個包含兩個計數(shù)器和兩個子組件的例子:
這里只會更新兩個 Display 組件之一的文本節(jié)點。未更新的文本節(jié)點在初始渲染后將永遠(yuǎn)不會打印。
實際上,我們無法在 React 中實現(xiàn)相同的效果,因為至少會有一個組件需要重新渲染。那么下面就來看看如何在 React 中記憶組件以最小化重新渲染的次數(shù)。
但即使進行了記憶,React 也會產(chǎn)生多次重新渲染:
如果沒有記憶,我們會看到:
這比 Signal 需要做的工作多得多。所以,這就是為什么 Signal 的工作就像把所有事情都記下來了,而不必由我們自己來記憶。
舉個例子
下面就來看一個實現(xiàn)購物車的例子(React):
APP 組件中定義了購物車的狀態(tài) cart 以及兩個子組件:
- Main 組件:通過多層組件傳遞 setCart 函數(shù),直到它到達購買按鈕。
- NavBar 組件:通過多層組件傳遞購物車狀態(tài),直到它到達渲染購物車的組件。
這里的問題就是每次點擊購買按鈕時,大部分組件樹都必須重新渲染。這會導(dǎo)致類似于的輸出:
如果使用記憶,那么就可以避免 Main 組件重新渲染,而只有 NavBar 組件重新渲染:
如果使用 Signal,輸出是這樣的:
這大大減少了需要執(zhí)行的代碼量。
總結(jié)
Signal 是在應(yīng)用中存儲狀態(tài)的一種方式,類似于 React 中的 useState()。兩者的關(guān)鍵區(qū)別在于,Signal 返回一個 getter 和一個 setter,而非響應(yīng)式系統(tǒng)只返回一個值和一個 setter。
Signal 是響應(yīng)式的,這意味著它需要跟蹤誰對狀態(tài)感興趣并通知訂閱者狀態(tài)更改。這是通過觀察調(diào)用狀態(tài) getter 的上下文來實現(xiàn)的,它創(chuàng)建了一個訂閱。
相比之下,React 中的 useState() 僅返回狀態(tài)值,這意味著它不知道如何使用狀態(tài)值,并且必須重新渲染整個組件樹以響應(yīng)狀態(tài)變化。
所以說 Signal 是前端框架的未來!