目錄
當我們談論反應性模型時,我們(首先也是最重要的)談論“
首頁 web前端 js教程 反應性是什麼鬼! ?

反應性是什麼鬼! ?

Dec 22, 2024 am 05:44 AM

反應模型解釋

前言

自從我開始開發應用程式和網站以來已經過去了 10 年,但 JavaScript 生態系統從未像今天這樣令人興奮!

2022 年,社群被「訊號」的概念所吸引,以至於大多數 JavaScript 框架都將它們整合到自己的引擎中。我正在考慮 Preact,它自 2022 年 9 月以來提供了與組件生命週期分離的反應變量;或者最近的 Angular,它於 2023 年 5 月實驗性地實現了 Signals,然後從版本 18 正式開始。其他 JavaScript 函式庫也選擇重新考慮他們的方法...

從 2023 年到現在,我一直在各個專案中使用 Signals。它們的實施和使用簡單性完全說服了我,以至於我在技術研討會、培訓課程和會議期間與我的專業網絡分享了它們的好處。

但最近,我開始問自己這個概念是否真正具有「革命性」/是否有訊號的替代品?因此,我更深入地研究了這種反思,並發現了反應式系統的不同方法。

這篇文章概述了不同的反應模型,以及我對它們如何運作的理解。

注意: 說到這裡,你可能已經猜到了,我不會討論Java 的「Reactive Streams」;否則,我會把這篇文章的標題定為「背壓是什麼鬼! 理論

當我們談論反應性模型時,我們(首先也是最重要的)談論“

反應性程式

”,但特別是“反應性”。

響應式程式設計

是一種開發範例,允許將資料來源的變更自動傳播給消費者。 因此,我們可以將

反應性

定義為根據資料的變化即時更新依賴關係的能力。

NB:簡而言之,當使用者填寫和/或提交表單時,我們必須對這些變更做出反應,顯示載入元件或任何其他指定正在發生的事情。 .. 另一個例子,當非同步接收資料時,我們必須透過顯示全部或部分資料、執行新操作等來做出反應 在這種情況下,反應式函式庫提供了自動更新和高效傳播的變量,使編寫簡單且最佳化的程式碼變得更加容易。

為了提高效率,當且僅當它們的值發生變化時,這些系統必須重新計算/重新評估這些變數!同樣,為了確保廣播的數據保持一致和最新,系統必須避免顯示任何中間狀態(特別是在狀態變化的計算期間)。

NB:狀態是指程式/應用程式整個生命週期中使用的資料/值。

好吧,但是…這些「反應模型」到底是什麼?

PUSH,又稱為「急切」反應

第一個反應模型稱為「PUSH」(或「渴望」反應)。本系統基於以下原則:

  • 資料來源的初始化(稱為「Observables」)
  • 元件/函數訂閱這些資料來源(這些是消費者)
  • 當數值改變時,資料會立即傳播給消費者(稱為「觀察者」)

如您可能已經猜到的,「PUSH」模型依賴「Observable/Observer」設計模式。

第一個用例:初始狀態和狀態更改

讓我們考慮以下初始狀態,

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
登入後複製
登入後複製
登入後複製
登入後複製

WTF Is Reactivity !?

使用反應式函式庫(例如 RxJS),這個初始狀態看起來比較像這樣:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
登入後複製
登入後複製
登入後複製
登入後複製

注意:為了這篇文章的目的,所有程式碼片段都應被視為「偽代碼」。

現在,我們假設消費者(例如元件)希望在資料來源更新時記錄狀態 D 的值,

d.subscribe((value) => console.log(value));
登入後複製
登入後複製
登入後複製

我們的元件將訂閱資料流;它仍然需要觸發改變,

a.next({ firstName: "Jane", lastName: "Doe" });
登入後複製
登入後複製

從那裡,「PUSH」系統偵測到變更並自動將其廣播給消費者。基於上面的初始狀態,以下是可能發生的操作的描述:

  • 資料來源A發生狀態變化!
  • A的值傳播到B(資料來源B的計算);
  • 然後,將B的值傳播到D(資料來源D的計算);
  • A的值傳播到C(資料來源C的計算);
  • 最後將C的值傳播到D(資料來源D的重新計算);

WTF Is Reactivity !?

該系統的挑戰之一在於計算順序。事實上,根據我們的用例,您會注意到 D 可能會被評估兩次:第一次使用 C 的先前狀態值;第二次使用 C 的值。第二次,C 的值是最新的!在這個反應模型中,這個挑戰被稱為「鑽石問題」 ️。

第二個用例:下一次迭代

現在,我們假設該狀態依賴兩個主要資料來源,

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
登入後複製
登入後複製
登入後複製
登入後複製

更新 E 時,系統將重新計算整個狀態,這使得系統可以透過覆蓋先前的狀態來保留單一事實來源。

  • 資料來源E發生狀態變化!
  • A的值傳播到B(資料來源B的計算);
  • 然後,將B的值傳播到D(資料來源D的計算);
  • A的值傳播到C(資料來源C的計算);
  • E的值傳播到C(資料來源C的重新計算);.
  • 最後將C的值傳播到D(資料來源D的重新計算);

WTF Is Reactivity !?

「鑽石問題」再次發生...這次是在資料來源 C 上,可能會評估 2 次,並且始終在 D 上。

鑽石問題

「鑽石問題」並不是「渴望」反應模型中的新挑戰。一些計算演算法(尤其是 MobX 使用的演算法)可以標記「反應式依賴樹的節點」以平衡狀態計算。透過這種方法,系統將首先評估「根」資料來源(在我們的範例中為 A 和 E),然後是 B 和 C,最後是 D。更改狀態計算的順序有助於解決此類問題。

WTF Is Reactivity !?

PULL,又稱「惰性」反應

第二個反應模型稱為「PULL」。與“PUSH”模型不同,它基於以下原則:

  • 反應變數的宣告
  • 系統延遲狀態計算
  • 派生狀態是根據其依賴關係計算的
  • 系統避免過度更新

最重要的是要記住最後一條規則:與先前的系統不同,最後一條規則推遲了狀態計算,以避免對相同資料來源進行多次評估。

第一個用例:初始狀態和狀態更改

讓我們保持之前的初始狀態...

WTF Is Reactivity !?

在這種系統中,初始狀態語法將採用以下形式:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
登入後複製
登入後複製
登入後複製
登入後複製

注意: React 愛好者可能會認識這個文法 ?

宣告一個反應變數給一個元組「誕生」:一方面是不可變的變數;另一個變數的更新函數。其餘語句(在我們的例子中為 B、C 和 D)被視為派生狀態,因為它們「監聽」各自的依賴關係。

d.subscribe((value) => console.log(value));
登入後複製
登入後複製
登入後複製

「惰性」系統的定義特徵是它不會立即傳播更改,而是僅在明確請求時傳播更改。

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
登入後複製
登入後複製
登入後複製
登入後複製

在「PULL」模型中,使用effect()(來自組件)來記錄反應變數(指定為依賴項)的值會觸發狀態變更的計算:

  • D 將檢查其依賴項(B 和 C)是否已更新;
  • B 將檢查其依賴項 (A) 是否已更新;
  • A 將其值傳播給 B(計算 B 的值);
  • C 將檢查其相依性 (A) 是否已更新;
  • A 會將其值傳播給 C(計算 C 的值)
  • B和C將各自的值傳播給D(計算D的值);

WTF Is Reactivity !?

在查詢依賴項時可以最佳化此系統。事實上,在上面的場景中,A 被查詢了兩次以確定它是否已更新。但是,第一個查詢可能足以定義狀態是否已變更。 C 不需要執行此操作...相反,A 只能廣播其值。

第二個用例:下一次迭代

讓我們透過加入第二個反應變數「root」來讓狀態稍微複雜一些,

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
登入後複製
登入後複製
登入後複製
登入後複製

系統再次延遲狀態計算,直到明確要求為止。使用與之前相同的效果,更新新的反應變數將觸發以下步驟:

  • D 將檢查其依賴項(B 和 C)是否已更新;
  • B 將檢查其依賴項 (A) 是否已更新;
  • C 將檢查其依賴項(A 和 E)是否已更新;
  • E 將其值傳播給 C,C 將透過記憶取得 A 的值(計算 C 的值);
  • C 將其值傳播給 D,D 將透過記憶取得 B 的值(計算 D 的值);

WTF Is Reactivity !?

由於 A 的值沒有改變,所以不需要重新計算這個變數(同樣的情況也適用於 B 的值)。在這種情況下,使用記憶演算法可以提高狀態計算期間的表現。

推拉,又稱「細粒度」反應

最後一個反應模型是「推拉」系統。術語「PUSH」反映了更改通知的立即傳播,而「PULL」指的是按需獲取狀態值。這種方法與所謂的「細粒度」反應性密切相關,它遵循以下原則:

  • 反應變數的聲明(我們談論的是反應基元)
  • 在原子層級追蹤依賴關係
  • 變更傳播具有高度針對性

請注意,這種反應性並非「推拉」模型所獨有。細粒度反應性是指對系統依賴性的精確追蹤。因此,還有 PUSHPULL 反應模型也以這種方式工作(我正在考慮 Jotai 或 Recoil。

第一個用例:初始狀態和狀態更改

仍基於先前的初始狀態...「細粒度」反應系統中初始狀態的聲明如下所示:

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
登入後複製
登入後複製
登入後複製
登入後複製

注意:signal關鍵字的使用不只是軼事

在語法方面,它與「PUSH」模型非常相似,但有一個顯著且重要的區別:依賴關係! 在「細粒度」反應系統中,沒有必要明確聲明計算派生狀態所需的依賴關係,因為這些狀態隱式追蹤它們使用的變數。在我們的例子中,B 和 C 將自動追蹤 A 值的更改,D 將追蹤 B 和 C 的更改。

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
登入後複製
登入後複製
登入後複製
登入後複製

在這樣的系統中,更新反應變數比基本的「PUSH」模型更有效,因為變更會自動傳播到依賴它的衍生變數(僅作為通知,而不是值本身) 。

d.subscribe((value) => console.log(value));
登入後複製
登入後複製
登入後複製

然後,根據需要(讓我們以logger 為例),系統內使用D 將取得關聯根狀態的值(在我們的例子中為A),計算值導出狀態( B和C),最後評估D。這不是一個直覺的操作方式嗎?

WTF Is Reactivity !?

第二個用例:下一次迭代

讓我們考慮以下狀態,

a.next({ firstName: "Jane", lastName: "Doe" });
登入後複製
登入後複製

再一次,推拉系統的「細粒度」方面允許自動追蹤每個狀態。因此,派生狀態 C 現在追蹤根狀態 A 和 E。更新變數 E 將觸發以下操作:

  • 反應原語 E 的狀態變化!
  • 目標變更通知(透過 C 從 E 到 D);
  • E將其值傳播給C,C將透過記憶檢索A的值(計算C的值);
  • C 會將其值傳播給 D,D 將透過 memoization 檢索 B 的值(計算 D 的值);

WTF Is Reactivity !?

這是反應性依賴關係相互之間的先前關聯,使得模型如此有效率!

確實,在經典的「PULL」系統(例如React 的Virtual DOM)中,當從元件更新響應式狀態時,框架將收到變更通知(觸發「 差異”階段)。然後,根據需要(和延遲),框架將通過遍歷反應式依賴樹來計算更改;每次更新變數時!這種對依賴狀態的「發現」需要付出巨大的代價......

透過「細粒度」反應性系統(如訊號),反應性變數/基元的更新會自動通知與它們相關的任何派生狀態的變化。因此,無需(重新)發現關聯的依賴關係;狀態傳播是有針對性的!

結論(.值)

到 2024 年,大多數 Web 框架都選擇重新思考它們的工作方式,特別是在反應性模型方面。這種轉變總體上提高了他們的效率和競爭力。其他人選擇(仍然)混合(我在這裡考慮的是 Vue),這使他們在許多情況下更加靈活。

最後,無論選擇什麼模型,在我看來,一個(好的)反應式系統是建立在一些主要規則之上的:

  1. 系統防止不一致的衍生狀態;
  2. 系統中使用狀態會導致反應式派生狀態;
  3. 系統盡量減少過多的工作;
  4. 並且,「對於給定的初始狀態,無論狀態遵循的路徑,系統的最終結果將始終相同!」

最後一點可以解釋為聲明式程式設計的基本原則,這就是我如何看待(好的)反應式系統需要確定性!這就是使反應式模型可靠、可預測且易於在大規模技術專案中使用的“決定論”,無論演算法有多複雜。

以上是反應性是什麼鬼! ?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1657
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1229
24
神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript:探索網絡語言的多功能性 JavaScript:探索網絡語言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

如何使用Next.js(前端集成)構建多租戶SaaS應用程序 如何使用Next.js(前端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

如何安裝JavaScript? 如何安裝JavaScript? Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

See all articles