偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

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

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

下面是正文。。。

這篇文章并不是關于響應式的權威歷史,而是關于我個人在這方面的經(jīng)歷和觀點。

Flex

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

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

[Bindable] 注解會創(chuàng)建一個 getter/setter,當屬性發(fā)生變化時,它會觸發(fā)事件。然后你可以監(jiān)聽屬性的變化。Flex 附帶了用于渲染 UI 的 .mxml 文件模板。如果屬性發(fā)生變化,.mxml 中的任何數(shù)據(jù)綁定都是細粒度的響應式,因為它通過監(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 并不是響應式最早出現(xiàn)的地方,但它是我第一次接觸到響應式。

在 Flex 中,響應式有點麻煩,因為它容易創(chuàng)建更新風暴。更新風暴是指當單個屬性變化觸發(fā)許多其他屬性(或模板)變化,從而觸發(fā)更多屬性變化,依此類推。有時,這會陷入無限循環(huán)。Flex 沒有區(qū)分更新屬性和更新 UI,導致大量的 UI 抖動(渲染中間值)。

事后看來,我可以看到哪些架構決策導致了這種次優(yōu)結果,但當時我并不清楚,我對響應式系統(tǒng)有點不信任。

AngularJS

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

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

<!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 對象都可以在模板中用作數(shù)據(jù)綁定源,更新也能正常工作。

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

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

React

React在AngularJS(Angular之前)之后推出,并進行了幾項改進。

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

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

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

粗粒度響應性

React 和 AngularJS 都是粗粒度響應式的。這意味著數(shù)據(jù)的變化會觸發(fā)大量的 JavaScript 執(zhí)行??蚣茏罱K會將所有的更改合并到 UI 中。這意味著快速變化的屬性,如動畫,可能會導致性能問題。

細粒度響應性

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

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

Backbone.js

Backbone 早于 AngularJS,它具有細粒度的響應性,但語法非常冗長。

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');

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

Knockout

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

但是 Knockout 有一個有趣的創(chuàng)新 —— 計算屬性,它可能已經(jī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);
};

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

Svelte

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

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

我認為擁有兩種不同的方法來實現(xiàn)同樣的事情并不理想,因為你必須在腦海中保持兩種不同的思維模式并在它們之間做出選擇。一種統(tǒng)一的方法會更受歡迎。

RxJS

RxJS 是一個不依賴于任何底層渲染系統(tǒng)的響應式庫。這似乎是一個優(yōu)勢,但它也有一個缺點。導航到新頁面需要拆除現(xiàn)有的 UI 并構建新的 UI。對于 RxJS,這意味著需要進行很多取消訂閱和訂閱操作。這些額外的工作意味著在這種情況下,粗粒度響應式系統(tǒng)會更快,因為拆除只是丟棄 UI(垃圾回收),而構建不需要注冊/分配監(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

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

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

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

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

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

SolidJS

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

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>
}

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

signals

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

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>
}

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

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

<Display value={10}/>

這樣做不會起作用,因為 Display 被定義為 Accessor:

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

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

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

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

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>
}

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

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

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

<Wrapper value={count()}/>

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

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

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

好處有:

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

我們還能在此基礎上做出什么改進嗎?

響應性和渲染

讓我們想象一個產品頁面,有一個購買按鈕和一個購物車。

圖片

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

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

圖片

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

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

圖片

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

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

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

圖片

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

Qwik

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

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

責任編輯:武曉燕 來源: 大遷世界
相關推薦

2021-08-12 18:48:31

響應式編程Bio

2017-09-21 10:58:05

顯示器凸面凹面

2022-06-16 13:08:30

Combine響應式編程訂閱

2019-04-11 15:45:08

ReactMixin前端

2016-11-28 16:23:23

戴爾

2023-12-20 14:44:33

軟件開發(fā)DevOpsNoOps

2010-03-10 18:12:50

Python編程語言

2023-06-02 16:28:01

2021-08-27 12:59:59

React前端命令

2022-09-01 08:00:00

響應式編程集成

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

設計原理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

2025-05-06 01:14:00

系統(tǒng)編程響應式

2017-09-12 15:26:44

2013-04-08 17:13:14

2024-04-26 08:17:09

GoGoogle項目
點贊
收藏

51CTO技術棧公眾號