自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Lisp到Vue、React再到 Qwit:響應(yīng)式編程的發(fā)展歷程

開發(fā) 前端
Qwik 可以將這個(gè)圖形序列化為 HTML。這使得客戶端完全可以跳過最初的“執(zhí)行世界以了解反應(yīng)圖”的步驟。我們稱這種能力為可恢復(fù)性。由于組件在客戶端上不會(huì)執(zhí)行或下載,因此 Qwik 的好處是應(yīng)用程序的即時(shí)啟動(dòng)。一旦應(yīng)用程序正在運(yùn)行,反應(yīng)就像 SolidJS 一樣精確。

本文介紹了響應(yīng)式編程的歷史和發(fā)展,響應(yīng)式編程是一種編程范式,它強(qiáng)調(diào)了數(shù)據(jù)流和變化的傳遞。文章從早期的編程語言開始講述,比如Lisp和Smalltalk,它們的數(shù)據(jù)結(jié)構(gòu)和函數(shù)式編程的特性促進(jìn)了響應(yīng)式編程的發(fā)展。然后,文章提到了響應(yīng)式編程框架的出現(xiàn),如React和Vue.js等。這些框架使用虛擬DOM(Virtual DOM)技術(shù)來跟蹤數(shù)據(jù)變化,并更新界面。文章還討論了響應(yīng)式編程的優(yōu)點(diǎn)和缺點(diǎn),如可讀性和性能等。最后,文章預(yù)測(cè)了未來響應(yīng)式編程的發(fā)展方向。

總的來說,本文很好地介紹了響應(yīng)式編程的歷史和發(fā)展,深入淺出地講述了它的優(yōu)點(diǎn)和缺點(diǎn)。文章提到了很多實(shí)際應(yīng)用和框架的例子,讓讀者更好地理解響應(yīng)式編程的概念和實(shí)踐。文章還預(yù)測(cè)了未來響應(yīng)式編程的發(fā)展方向,這對(duì)讀者和開發(fā)者有很大的啟示作用。

下面是正文。。。

這篇文章并不是關(guān)于響應(yīng)式的權(quán)威歷史,而是關(guān)于我個(gè)人在這方面的經(jīng)歷和觀點(diǎn)。

Flex

我的旅程始于 Macromedia Flex,后來被 Adobe 收購(gòu)。Flex 是基于 Flash 上的 ActionScript 的一個(gè)框架。ActionScript 與 JavaScript 非常相似,但它具有注解功能,允許編譯器為訂閱包裝字段。我不記得確切的語法了,也在網(wǎng)上找不到太多信息,但它看起來是這樣的:

class MyComponent {
[Bindable] public var name: String;
}

[Bindable] 注解會(huì)創(chuàng)建一個(gè) getter/setter,當(dāng)屬性發(fā)生變化時(shí),它會(huì)觸發(fā)事件。然后你可以監(jiān)聽屬性的變化。Flex 附帶了用于渲染 UI 的 .mxml 文件模板。如果屬性發(fā)生變化,.mxml 中的任何數(shù)據(jù)綁定都是細(xì)粒度的響應(yīng)式,因?yàn)樗ㄟ^監(jiān)聽屬性的變化。

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:MyComponent>
<mx:Label text="{name}"/></mx:Label>
</mx:MyComponent>
</mx:Applicatio>

我懷疑 Flex 并不是響應(yīng)式最早出現(xiàn)的地方,但它是我第一次接觸到響應(yīng)式。

在 Flex 中,響應(yīng)式有點(diǎn)麻煩,因?yàn)樗菀讋?chuàng)建更新風(fēng)暴。更新風(fēng)暴是指當(dāng)單個(gè)屬性變化觸發(fā)許多其他屬性(或模板)變化,從而觸發(fā)更多屬性變化,依此類推。有時(shí),這會(huì)陷入無限循環(huán)。Flex 沒有區(qū)分更新屬性和更新 UI,導(dǎo)致大量的 UI 抖動(dòng)(渲染中間值)。

事后看來,我可以看到哪些架構(gòu)決策導(dǎo)致了這種次優(yōu)結(jié)果,但當(dāng)時(shí)我并不清楚,我對(duì)響應(yīng)式系統(tǒng)有點(diǎn)不信任。

AngularJS

AngularJS 的最初目標(biāo)是擴(kuò)展 HTML 詞匯,以便設(shè)計(jì)師(非開發(fā)人員)可以構(gòu)建簡(jiǎn)單的 Web 應(yīng)用程序。這就是為什么 AngularJS 最終采用了 HTML 標(biāo)記的原因。由于 AngularJS 擴(kuò)展了 HTML,它需要綁定到任何 JavaScript 對(duì)象。那時(shí)候既沒有 Proxy、getter/setters,也沒有 Object.observe() 這些選項(xiàng)可供選擇。所以唯一可用的解決方案就是使用臟檢查。

臟檢查通過在瀏覽器執(zhí)行任何異步工作時(shí)讀取模板中綁定的所有屬性來工作。

<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>

這種方法的好處是,任何 JavaScript 對(duì)象都可以在模板中用作數(shù)據(jù)綁定源,更新也能正常工作。

缺點(diǎn)是每次更新都要執(zhí)行大量的 JavaScript。而且,因?yàn)?AngularJS 不知道何時(shí)可能發(fā)生變化,所以它運(yùn)行臟檢查的頻率遠(yuǎn)遠(yuǎn)超過理論上所需。

因?yàn)?AngularJS 可以與任何對(duì)象一起工作,而且它本身是 HTML 語法的擴(kuò)展,所以 AngularJS 從未將任何狀態(tài)管理形式固化。

React

React在AngularJS(Angular之前)之后推出,并進(jìn)行了幾項(xiàng)改進(jìn)。

首先,React引入了setState()。這使得React知道何時(shí)應(yīng)該對(duì)vDOM進(jìn)行臟檢查。這樣做的好處是,與每個(gè)異步任務(wù)都運(yùn)行臟檢查的AngularJS不同,React只有在開發(fā)人員告訴它要運(yùn)行時(shí)才會(huì)執(zhí)行。因此,盡管React vDOM的臟檢查比AngularJS更耗費(fèi)計(jì)算資源,但它會(huì)更少地運(yùn)行。

function Counter() {
const [count, setCount] = useState();
return <button onClick={() => setCount(count+1)}>{count}</button>
}

其次,React引入了從父組件到子組件的嚴(yán)格數(shù)據(jù)流。這是朝著框架認(rèn)可的狀態(tài)管理邁出的第一步,而AngularJS則沒有這樣做。

粗粒度響應(yīng)性

React 和 AngularJS 都是粗粒度響應(yīng)式的。這意味著數(shù)據(jù)的變化會(huì)觸發(fā)大量的 JavaScript 執(zhí)行。框架最終會(huì)將所有的更改合并到 UI 中。這意味著快速變化的屬性,如動(dòng)畫,可能會(huì)導(dǎo)致性能問題。

細(xì)粒度響應(yīng)性

解決上述問題的方法是細(xì)粒度響應(yīng)性,狀態(tài)改變只更新與狀態(tài)綁定的 UI 部分。

難點(diǎn)在于如何以良好的開發(fā)體驗(yàn)(DX)來監(jiān)聽屬性變化。

Backbone.js

Backbone 早于 AngularJS,它具有細(xì)粒度的響應(yīng)性,但語法非常冗長(zhǎng)。

var MyModel = Backbone.Model.extend({
initialize: function() {
// Listen to changes on itself.
this.on('change:name', this.onAsdChange);
},
onNameChange: function(model, value) {
console.log('Model: Name was changed to:', value);
}
});
var myModel = new MyModel();
myModel.set('name', 'something');

我認(rèn)為冗長(zhǎng)的語法是像 AngularJS 和后來的 React 這樣的框架取而代之的原因之一,因?yàn)殚_發(fā)者可以簡(jiǎn)單地使用點(diǎn)符號(hào)來訪問和設(shè)置狀態(tài),而不是一組復(fù)雜的函數(shù)回調(diào)。在這些較新的框架中開發(fā)應(yīng)用程序更容易,也更快。

Knockout

Knockout 和 AngularJS 出現(xiàn)在同一時(shí)期。我從未使用過它,但我的理解是它也受到了更新風(fēng)暴問題的困擾。雖然它在 Backbone.js 的基礎(chǔ)上有所改進(jìn),但與可觀察屬性一起使用仍然很笨拙,這也是我認(rèn)為開發(fā)者更喜歡像 AngularJS 和 React 這樣的點(diǎn)符號(hào)框架的原因。

但是 Knockout 有一個(gè)有趣的創(chuàng)新 —— 計(jì)算屬性,它可能已經(jīng)存在過,但這是我第一次聽說。它們會(huì)自動(dòng)在輸入上創(chuàng)建訂閱。

var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function() {
// Knockout tracks dependencies automatically.
// It knows that fullName depends on firstName and lastName,
// because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};

請(qǐng)注意,當(dāng) ko.pureComputed() 調(diào)用 this.firstName() 時(shí),值的調(diào)用會(huì)隱式地創(chuàng)建一個(gè)訂閱。這是通過 ko.pureComputed() 設(shè)置一個(gè)全局變量來實(shí)現(xiàn)的,這個(gè)全局變量允許 this.firstName() 與 ko.pureComputed() 通信,并將訂閱信息傳遞給它,而無需開發(fā)者進(jìn)行任何額外的工作。

Svelte

Svelte使用編譯器實(shí)現(xiàn)了響應(yīng)式。這里的優(yōu)勢(shì)在于,有了編譯器,語法可以是任何你想要的。你不受JavaScript的限制。對(duì)于組件,Svelte具有非常自然的響應(yīng)式語法。但是,Svelte并不會(huì)編譯所有文件,只會(huì)編譯以.svelte結(jié)尾的文件。如果你希望在未經(jīng)過編譯的文件中獲得響應(yīng)性,則Svelte提供了一個(gè)存儲(chǔ)API,它缺少已編譯響應(yīng)性所具有的魔力,并需要更明確地注冊(cè)使用subscribe和unsubscribe。

const count = writable(0);
const unsubscribe = count.subscribe(value => {
countValue = value;
});

我認(rèn)為擁有兩種不同的方法來實(shí)現(xiàn)同樣的事情并不理想,因?yàn)槟惚仨氃谀X海中保持兩種不同的思維模式并在它們之間做出選擇。一種統(tǒng)一的方法會(huì)更受歡迎。

RxJS

RxJS 是一個(gè)不依賴于任何底層渲染系統(tǒng)的響應(yīng)式庫(kù)。這似乎是一個(gè)優(yōu)勢(shì),但它也有一個(gè)缺點(diǎn)。導(dǎo)航到新頁面需要拆除現(xiàn)有的 UI 并構(gòu)建新的 UI。對(duì)于 RxJS,這意味著需要進(jìn)行很多取消訂閱和訂閱操作。這些額外的工作意味著在這種情況下,粗粒度響應(yīng)式系統(tǒng)會(huì)更快,因?yàn)椴鸪皇莵G棄 UI(垃圾回收),而構(gòu)建不需要注冊(cè)/分配監(jiān)聽器。我們需要的是一種批量取消訂閱/訂閱的方法。

const observable1 = interval(400);
const observable2 = interval(300);
const subscription = observable1.subscribe(x => console.log('[first](https://rxjs.dev/api/index/function/first): ' + x));
const childSubscription = observable2.subscribe(x => console.log('second: ' + x));
subscription.add(childSubscription);
setTimeout(() => {
// Unsubscribes BOTH subscription and childSubscription
subscription.unsubscribe();
}, 1000);

Vue 和 MobX

大約在同一時(shí)間,Vue 和 MobX 都開始嘗試基于代理的響應(yīng)式。代理的優(yōu)勢(shì)在于,你可以使用開發(fā)者喜歡的干凈的點(diǎn)表示法語法,同時(shí)可以像 Knockout 一樣使用相同的技巧來創(chuàng)建自動(dòng)訂閱 —— 這是一個(gè)巨大的勝利!

<template>
<button @click="count = count + 1">{{ count }}</button>
</template>

<script setup>
import { ref } from "vue";

const count = ref(1);
</script>

在上面的示例中,模板在渲染期間通過讀取 count 值自動(dòng)創(chuàng)建了一個(gè)對(duì) count 的訂閱。開發(fā)者無需進(jìn)行任何額外的工作。

SolidJS

SolidJS 的缺點(diǎn)是無法將引用傳遞給 getter/setter。你要么傳遞整個(gè)代理,要么傳遞屬性的值,但是你無法從存儲(chǔ)中剝離一個(gè) getter 并傳遞它。以此為例來說明這個(gè)問題。

function App() {
const state = createStateProxy({count: 1});
return (
<>
<button onClick={() => state.count++}>+1</button>\
<Wrapper value={state.count}/>
</>
);
}

function Wrapper(props) {
return <Display value={state.value}/>
}
function Display(props) {
return <span>Count: {props.value}</span>
}

當(dāng)我們讀取 state.count 時(shí),得到的數(shù)字是原始的,不再是可觀察的。這意味著 Middle 和 Child 都需要在 state.count 改變時(shí)重新渲染。我們失去了細(xì)粒度的響應(yīng)性。理想情況下,只有 Count: 應(yīng)該被更新。我們需要的是一種傳遞值引用而不是值本身的方法。

signals

signals 允許你不僅引用值,還可以引用該值的 getter/setter。因此,你可以使用信號(hào)解決上述問題:

function App() {
const [count, setCount] = createSignal(1);
return (
<>
<button onClick={() => setCount(count() + 1)}>+1</button>
<Wrapper value={count}/>
</>
);
}
function Wrapper(props: {value: Accessor<number>}) {
return <Display value={props.value}/>
}
function Display(props: {value: Accessor<number>}) {
return <span>Count: {props.value}</span>
}

這種解決方案的好處在于,我們不是傳遞值,而是傳遞一個(gè) Accessor(一個(gè) getter)。這意味著當(dāng) count 的值發(fā)生更改時(shí),我們不必經(jīng)過 Wrapper 和 Display,可以直接到達(dá) DOM 進(jìn)行更新。它的工作方式非常類似于 Knockout,但在語法上類似于 Vue/MobX。

假設(shè)我們想要綁定到一個(gè)常量作為組件的用戶,則會(huì)出現(xiàn) DX 問題。

<Display value={10}/>

這樣做不會(huì)起作用,因?yàn)?nbsp;Display 被定義為 Accessor:

function Display(props: {value: Accessor<number>});

這是令人遺憾的,因?yàn)榻M件的作者現(xiàn)在定義了使用者是否可以發(fā)送getter或 value。無論作者選擇什么,總會(huì)有未涵蓋的用例。這兩者都是合理的事情。

<Display value={10}/>
<Display value={createSignal(10)}/>

以上是使用 Display 的兩種有效方式,但它們都不能同時(shí)成立!我們需要一種方法來將類型聲明為基本類型,但可以同時(shí)與基本類型和 Accessor 一起使用。這時(shí)編譯器就出場(chǎng)了。

function App() {
const [count, setCount] = createSignal(1);
return (
<>
<button onClick={() => setCount(count() + 1)}>+1</button>
<Wrapper value={count()}/>
</>
);
}
function Wrapper(props: {value: number}) {
return <Display value={props.value}/>
}
function Display(props: {value: number}) {
return <span>Count: {props.value}</span>
}

請(qǐng)注意,現(xiàn)在我們聲明的是 number,而不是 Accessor。這意味著這段代碼將正常工作

<Display value={10}/>
<Display value={createSignal(10)()}/> // Notice the extra ()

但這是否意味著我們現(xiàn)在已經(jīng)破壞了響應(yīng)性?答案是肯定的,除非我們可以讓編譯器執(zhí)行一個(gè)技巧來恢復(fù)我們的響應(yīng)性。問題就出在這行代碼上:

<Wrapper value={count()}/>

count()的調(diào)用會(huì)將訪問器轉(zhuǎn)換為原始值并創(chuàng)建一個(gè)訂閱。因此編譯器會(huì)執(zhí)行這個(gè)技巧。

Wrapper({
get value() { return count(); }
})

通過在將count()作為屬性傳遞給子組件時(shí),在getter中包裝它,編譯器成功地延遲了對(duì)count()的執(zhí)行,直到DOM實(shí)際需要它。這使得DOM可以創(chuàng)建基礎(chǔ)信號(hào)的訂閱,即使對(duì)開發(fā)人員來說似乎是傳遞了一個(gè)值。

好處有:

  • 清晰的語法
  • 自動(dòng)訂閱和取消訂閱
  • 組件接口不必選擇原始類型或Accessor。
  • 響應(yīng)性即使開發(fā)人員將Accessor轉(zhuǎn)換為原始類型也能正常工作。

我們還能在此基礎(chǔ)上做出什么改進(jìn)嗎?

響應(yīng)性和渲染

讓我們想象一個(gè)產(chǎn)品頁面,有一個(gè)購(gòu)買按鈕和一個(gè)購(gòu)物車。

圖片

在上面的示例中,我們有一個(gè)樹形結(jié)構(gòu)中的組件集合。用戶可能采取的一種可能的操作是點(diǎn)擊購(gòu)買按鈕,這需要更新購(gòu)物車。對(duì)于需要執(zhí)行的代碼,有兩種不同的結(jié)果。

在粗粒度響應(yīng)式系統(tǒng)中,它是這樣的:

圖片

我們必須找到 Buy  和 Cart 組件之間的共同根,因?yàn)闋顟B(tài)很可能附加在那里。然后,在更改狀態(tài)時(shí),與該狀態(tài)相關(guān)聯(lián)的樹必須重新渲染。使用 memoization 技術(shù),可以將樹剪枝成僅包含上述兩個(gè)最小路徑。尤其是隨著應(yīng)用程序變得越來越復(fù)雜,需要執(zhí)行大量代碼。

在細(xì)粒度反應(yīng)式系統(tǒng)中,它看起來像這樣:

圖片

請(qǐng)注意,只有目標(biāo) Cart 需要執(zhí)行。無需查看狀態(tài)是在哪里聲明的或共同祖先是什么。也不必?fù)?dān)心數(shù)據(jù)記憶化以修剪樹。精細(xì)的反應(yīng)式系統(tǒng)的好處在于,開發(fā)人員無需任何努力,運(yùn)行時(shí)只執(zhí)行最少量的代碼!

精細(xì)的反應(yīng)式系統(tǒng)的手術(shù)精度使它們非常適合懶惰執(zhí)行代碼,因?yàn)橄到y(tǒng)只需要執(zhí)行狀態(tài)的偵聽器(在我們的例子中是 Cart)。

但是,精細(xì)的反應(yīng)式系統(tǒng)有一個(gè)意外的角落案例。為了建立反應(yīng)圖,系統(tǒng)必須至少執(zhí)行所有組件以了解它們之間的關(guān)系!一旦建立起來,系統(tǒng)就可以進(jìn)行手術(shù)。這是初始執(zhí)行的樣子:

圖片

你看出問題了嗎?我們想懶惰地下載和執(zhí)行,但反應(yīng)圖的初始化強(qiáng)制執(zhí)行應(yīng)用程序的完整下載。

Qwik

這就是 Qwik 發(fā)揮作用的地方。Qwik 是精細(xì)的反應(yīng)式,類似于 SolidJS,意味著狀態(tài)的變化直接更新 DOM。(在某些角落情況下,Qwik 可能需要執(zhí)行整個(gè)組件。)但是 Qwik 有一個(gè)詭計(jì)。記得精細(xì)的反應(yīng)性要求所有組件至少執(zhí)行一次以創(chuàng)建反應(yīng)圖嗎?好吧,Qwik 利用了組件在 SSG 期間已經(jīng)在服務(wù)器上執(zhí)行的事實(shí)。Qwik 可以將這個(gè)圖形序列化為 HTML。這使得客戶端完全可以跳過最初的“執(zhí)行世界以了解反應(yīng)圖”的步驟。我們稱這種能力為可恢復(fù)性。由于組件在客戶端上不會(huì)執(zhí)行或下載,因此 Qwik 的好處是應(yīng)用程序的即時(shí)啟動(dòng)。一旦應(yīng)用程序正在運(yùn)行,反應(yīng)就像 SolidJS 一樣精確。

原文:https://www.builder.io/blog/history-of-reactivity

責(zé)任編輯:武曉燕 來源: 大遷世界
相關(guān)推薦

2021-08-12 18:48:31

響應(yīng)式編程Bio

2022-06-16 13:08:30

Combine響應(yīng)式編程訂閱

2017-09-21 10:58:05

顯示器凸面凹面

2019-04-11 15:45:08

ReactMixin前端

2016-11-28 16:23:23

戴爾

2023-06-02 16:28:01

2023-12-20 14:44:33

軟件開發(fā)DevOpsNoOps

2010-03-10 18:12:50

Python編程語言

2021-08-27 12:59:59

React前端命令

2022-09-01 08:00:00

響應(yīng)式編程集成

2011-05-25 14:59:35

if elseswitch case

2021-07-05 06:51:44

Java 企業(yè)版編程

2020-08-13 17:18:20

Kubernetes邊緣容器

2021-01-25 05:38:04

設(shè)計(jì)原理VueSubject

2020-05-17 13:59:37

物聯(lián)網(wǎng)工業(yè)物聯(lián)網(wǎng)工業(yè)4.0

2011-08-15 10:13:12

2013-04-08 17:13:14

2017-09-12 15:26:44

2017-05-23 16:36:06

程序程序員

2024-04-26 08:17:09

GoGoogle項(xiàng)目
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)