搜索
首页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
在JavaScript中替换字符串字符在JavaScript中替换字符串字符Mar 11, 2025 am 12:07 AM

JavaScript字符串替换方法详解及常见问题解答 本文将探讨两种在JavaScript中替换字符串字符的方法:在JavaScript代码内部替换和在网页HTML内部替换。 在JavaScript代码内部替换字符串 最直接的方法是使用replace()方法: str = str.replace("find","replace"); 该方法仅替换第一个匹配项。要替换所有匹配项,需使用正则表达式并添加全局标志g: str = str.replace(/fi

构建您自己的Ajax Web应用程序构建您自己的Ajax Web应用程序Mar 09, 2025 am 12:11 AM

因此,在这里,您准备好了解所有称为Ajax的东西。但是,到底是什么? AJAX一词是指用于创建动态,交互式Web内容的一系列宽松的技术。 Ajax一词,最初由Jesse J创造

10个JQuery Fun and Games插件10个JQuery Fun and Games插件Mar 08, 2025 am 12:42 AM

10款趣味横生的jQuery游戏插件,让您的网站更具吸引力,提升用户粘性!虽然Flash仍然是开发休闲网页游戏的最佳软件,但jQuery也能创造出令人惊喜的效果,虽然无法与纯动作Flash游戏媲美,但在某些情况下,您也能在浏览器中获得意想不到的乐趣。 jQuery井字棋游戏 游戏编程的“Hello world”,现在有了jQuery版本。 源码 jQuery疯狂填词游戏 这是一个填空游戏,由于不知道单词的上下文,可能会产生一些古怪的结果。 源码 jQuery扫雷游戏

如何创建和发布自己的JavaScript库?如何创建和发布自己的JavaScript库?Mar 18, 2025 pm 03:12 PM

文章讨论了创建,发布和维护JavaScript库,专注于计划,开发,测试,文档和促销策略。

jQuery视差教程 - 动画标题背景jQuery视差教程 - 动画标题背景Mar 08, 2025 am 12:39 AM

本教程演示了如何使用jQuery创建迷人的视差背景效果。 我们将构建一个带有分层图像的标题横幅,从而创造出令人惊叹的视觉深度。 更新的插件可与JQuery 1.6.4及更高版本一起使用。 下载

Matter.js入门:简介Matter.js入门:简介Mar 08, 2025 am 12:53 AM

Matter.js是一个用JavaScript编写的2D刚体物理引擎。此库可以帮助您轻松地在浏览器中模拟2D物理。它提供了许多功能,例如创建刚体并为其分配质量、面积或密度等物理属性的能力。您还可以模拟不同类型的碰撞和力,例如重力摩擦力。 Matter.js支持所有主流浏览器。此外,它也适用于移动设备,因为它可以检测触摸并具有响应能力。所有这些功能都使其值得您投入时间学习如何使用该引擎,因为这样您就可以轻松创建基于物理的2D游戏或模拟。在本教程中,我将介绍此库的基础知识,包括其安装和用法,并提供一

使用jQuery和Ajax自动刷新DIV内容使用jQuery和Ajax自动刷新DIV内容Mar 08, 2025 am 12:58 AM

本文演示了如何使用jQuery和ajax自动每5秒自动刷新DIV的内容。 该示例从RSS提要中获取并显示了最新的博客文章以及最后的刷新时间戳。 加载图像是选择

如何在浏览器中优化JavaScript代码以进行性能?如何在浏览器中优化JavaScript代码以进行性能?Mar 18, 2025 pm 03:14 PM

本文讨论了在浏览器中优化JavaScript性能的策略,重点是减少执行时间并最大程度地减少对页面负载速度的影响。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
2 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
2 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),