搜索
首页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,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

node.js流带打字稿node.js流带打字稿Apr 30, 2025 am 08:22 AM

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python vs. JavaScript:性能和效率注意事项Python vs. JavaScript:性能和效率注意事项Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript的起源:探索其实施语言JavaScript的起源:探索其实施语言Apr 29, 2025 am 12:51 AM

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

幕后:什么语言能力JavaScript?幕后:什么语言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。

Python和JavaScript的未来:趋势和预测Python和JavaScript的未来:趋势和预测Apr 27, 2025 am 12:21 AM

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

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脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器