Signal 即將成為JavaScript的一部分
什么是響應(yīng)性?
在過去的幾年中,響應(yīng)性成為了所有現(xiàn)代前端框架以及React庫的核心。
對于不熟悉前端開發(fā)的人來說,起初這可能是一個(gè)令人困惑的概念,因?yàn)樗淖兞顺R?guī)的、自上而下的、從調(diào)用者到被調(diào)用者的順序工作流。
在響應(yīng)性范式中,當(dāng)事情發(fā)生變化時(shí),數(shù)據(jù)會(huì)自動(dòng)更新,下面簡單展示一下偽代碼:
effect: console.log(x+2)
x = 2
x = 3
這種方法允許開發(fā)者以一種更直觀的方式處理數(shù)據(jù)和UI的更新,使得用戶界面能夠?qū)崟r(shí)反映底層數(shù)據(jù)的變化。
根據(jù)響應(yīng)性范式,應(yīng)用程序可以在變量x發(fā)生變化時(shí),無需程序員的特別努力,就能顯示NaN、4和5等多種結(jié)果。
這里極度簡化了這個(gè)話題,只是為了給那些對響應(yīng)性一無所知的人一個(gè)大致的了解。
需要指出的是,響應(yīng)性在前端開發(fā)中的許多情況下都非常有用。例如,在顯示用戶界面時(shí),一個(gè)框架可以識(shí)別變量何時(shí)發(fā)生變化以更新用戶界面。比如當(dāng)你展示一個(gè)實(shí)時(shí)的溫度計(jì)時(shí)。
響應(yīng)性的應(yīng)用場景遠(yuǎn)不止于此,幾乎所有的前端框架都在使用響應(yīng)性技術(shù)。甚至還有一個(gè)響應(yīng)式的jQuery庫。
如何實(shí)現(xiàn)響應(yīng)性?
響應(yīng)性在實(shí)際應(yīng)用中的一個(gè)很好的例子是電子表格。當(dāng)你在電子表格中基于其他單元格的值輸入一個(gè)計(jì)算公式時(shí),你不需要手動(dòng)更新計(jì)算結(jié)果。電子表格應(yīng)用會(huì)自動(dòng)檢測到變化,并更新所有基于修改過的單元格的計(jì)算值。
在前端框架中,實(shí)現(xiàn)響應(yīng)性有多種方法。一個(gè)著名的例子是使用RxJS,它依賴于觀察者(Observables)、訂閱者(Subscribers)和主題(Subjects)。如果你經(jīng)常處理異步數(shù)據(jù)流,這是一個(gè)很棒的庫。
在各種框架中,響應(yīng)性的復(fù)雜性對開發(fā)者來說是隱藏的。
在React中,你必須明確提供依賴列表:
圖片
一旦reactive roomId 或 serverUrl 發(fā)生變化,上面的代碼片段將會(huì)斷開當(dāng)前房間并連接到新的房間。
在Vue中,你甚至不需要提供依賴列表:
圖片
Vue會(huì)自行判斷,如果copy.count發(fā)生變化,就應(yīng)該重新運(yùn)行代碼。
如果我們審視所有主要的框架和庫,會(huì)發(fā)現(xiàn)每個(gè)框架和庫中的響應(yīng)性幾乎達(dá)到了相同的API水平,并且從用戶的角度看幾乎在做相同的事情。
Signals 的起源如何?
Angular進(jìn)行的最后一次主要切換到類似的API是在2023年4月3日發(fā)布的一份RFC中詳細(xì)說明的。該文檔深入探討了為什么zone.js的反應(yīng)性不足,并且Angular必須轉(zhuǎn)向更細(xì)粒度的Signals。
并不是說Signals在這方面完全是新事物,更多的是基于研究如何以最佳方式提供響應(yīng)性的結(jié)論。我真的認(rèn)為應(yīng)該將Signals的來源歸功于Solid。
自從那次重大工作完成以后,它鞏固了整個(gè)社區(qū)對細(xì)粒度響應(yīng)性的采納。因?yàn)閺哪菚r(shí)起我們知道,幾乎每個(gè)框架和庫都使用幾乎相同的響應(yīng)性模型,但有些用的是tomato,有些則用的是tomato。
推動(dòng) Signal 標(biāo)準(zhǔn)化的努力
考慮到這一點(diǎn),我們可以想象許多可能的結(jié)果。一切可能維持現(xiàn)狀,每個(gè)框架使用自己的響應(yīng)性模型。在這種情況下,人們實(shí)際上在使用不同名稱下的相同事物。
另一個(gè)情景是,每個(gè)人突然使用提供響應(yīng)性模型的庫。到目前為止,這還沒有發(fā)生,但它仍然是桌面上的一個(gè)選項(xiàng)。
第三個(gè),也是最瘋狂的情景,將是將 signals 納入 Ecmascript 本身。這意味著它將成為 JavaScript 本身的一部分。
聽起來瘋狂嗎?
確實(shí)。
但一切都在2023年2月一次偶然的 Twitter 聊天中浮出水面:每個(gè)人已經(jīng)有或正在調(diào)整響應(yīng)模型以基于 Signals 的模型。參與討論的人之一是 Daniel Ehrenberg。
在過去的一年中,你真的不知道是否有什么事情正在醞釀,直到一個(gè)月前。
那時(shí),Daniel 在紐約提議了一個(gè)名為 queerjs 的演講,這個(gè)名字相當(dāng)令人費(fèi)解:
他提供了一個(gè)相當(dāng)引人入勝的描述:
一種選擇是通過使 Signals 內(nèi)置到 JavaScript 中,如 Promises,來穩(wěn)定生態(tài)系統(tǒng),提高性能和互操作性。但是...最好避免像 Promises 那樣的一些陷阱,例如它們始終開啟的調(diào)度器。
這意味著,Daniel 真的在考慮 Signals 是否應(yīng)該成為 JavaScript 的一部分!
Signals可能會(huì)成為JavaScript的一部分
總的來說,這些都還只是討論階段。但是在JavaScript世界里,事情發(fā)展迅速。甚至比C++成為一種內(nèi)存安全的編程語言還要快,這是肯定的!
兩天前,Daniel提議在4月的TC39會(huì)議上討論這個(gè)主題。這意味著它可能很快就會(huì)進(jìn)入第一階段!
這是一件大事,因?yàn)門C39,即技術(shù)委員會(huì)39,負(fù)責(zé)標(biāo)準(zhǔn)化Ecmascript,而一旦某樣?xùn)|西成為標(biāo)準(zhǔn)的一部分,遲早會(huì)成為JavaScript的一部分!
當(dāng)然,這不會(huì)像在周五晚上發(fā)布到生產(chǎn)環(huán)境那么簡單。
Signals 可能需要時(shí)間才能成為 JavaScript 的標(biāo)準(zhǔn)功能
Signals 要成為 JavaScript 的標(biāo)準(zhǔn)功能,需要經(jīng)過六個(gè)標(biāo)準(zhǔn)化階段。因此,鑒于 Signals 的復(fù)雜性和響應(yīng)性,這對整個(gè)委員會(huì)來說是一項(xiàng)艱巨的工作。但同時(shí),它也將吸引包括前端框架開發(fā)者在內(nèi)的許多開發(fā)者的關(guān)注。
在最佳情況下,他們所有人都應(yīng)該滿意于將來某天切換到 Signals。
但這需要仔細(xì)的工作和解決許多難題。例如,標(biāo)準(zhǔn)應(yīng)該是開放的還是封閉的。
展望未來幾年,我們可能會(huì)發(fā)現(xiàn)每個(gè)框架都使用標(biāo)準(zhǔn)的 JavaScript Signals。
這將有助于提高性能、可維護(hù)性,并能夠?qū)⒅R(shí)從一個(gè)框架轉(zhuǎn)移到另一個(gè)框架。
許多好處確實(shí)可以讓這種努力變得值得。
同時(shí),這個(gè)過程也可能在任何時(shí)候被終止,所以目前一切皆有可能。
然而,我無法停止這種感覺,現(xiàn)在是推動(dòng) Signals 前進(jìn)的絕佳時(shí)機(jī)。特別是知道從2024年3月18日開始,還有一個(gè)Observable/Subscription模型的孵化過程,Signals 可以從中受益。
Signal 提案剛剛發(fā)布!
Rob Eisenberg 和 Daniel Ehrenberg 剛剛發(fā)布了 Signal 標(biāo)準(zhǔn)的第0階段提案。
已經(jīng)有一個(gè) polyfill 可供測試,而這個(gè)提案已經(jīng)秘密開發(fā)了8個(gè)月!
作者成功地吸引了來自各種框架和庫的作者參與:
他們在這里合作,開發(fā)了一個(gè)可以支持他們核心響應(yīng)性需求的通用模型。當(dāng)前的草案基于來自 Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz 等作者/維護(hù)者的設(shè)計(jì)輸入。
以下是一個(gè)如何使用 Signals 的例子:
const counter = new Signal.State(0);
const isEven = new Signal.Computed(
() => (counter.get() & 1) == 0);
const parity = new Signal.Computed(
() => isEven.get() ? "even" : "odd");
// 庫或框架定義基于其他 Signal 原語的效果
declare function effect(cb: () => void):
(() => void);
effect(() => element.innerText
= parity.get());
// 模擬對計(jì)數(shù)器的外部更新...
setInterval(() => counter.set(
counter.get() + 1), 1000);
這是多么令人震驚的簡單易用啊!