主题
在本文中,我们将深入探讨如何在 Solid 中使用信号,这是一个现代的响应式 JavaScript 库,用于构建主要依赖组件的用户界面,更纯粹的状态监控机制。
原文: Signals: Fine-grained Reactivity for JavaScript Frameworks — SitePoint
内容:
- 信号简介
- 信号的使用地点
- 什么是固体?
- 信号到底是什么?
- 信号示例
- 角度信号
- 固体的其他特点
信号简介
Web开发的最新趋势之一是信号的使用, 它提供了一种更具反应性的方法来更新程序中可能会更改的值。更新值时,使用该值的所有内容也会更新。这就是信号如此独特的原因。
信号的增长以及对它们的兴趣让人想起 16 年迎接 React 8.2019 版本的所有骚动,当时 React 团队引入了钩子。钩子的目的是使状态更新(以及最终的所有更新)在方法上更实用,并摆脱使用类。虽然信号看起来与钩子几乎相同,但有一些细微的差异使它们与众不同(我们将在下面探讨)。
信号的使用地点
什么是固体?
Solid(也称为SolidJS)由Ryan Carniato于2016年创建,并于2018年发布。用他自己的话说,它“源于继续使用我从Knockout.js中喜欢的细粒度反应模式的愿望。
他不喜欢像 React 和 Vue 这样的库当时所走的方向,并且“只是更喜欢使用比组件小且独立于组件的原语所带来的控制和可组合性。他的解决方案是创建Solid,这是一个使用信号来创建细粒度反应性的响应式框架 - 这种信号模式现在也可以在许多其他框架中看到。
乍一看,Solid 看起来很像带有钩子和功能组件的 React。在某些方面,这是正确的:在管理数据方面,它们都具有相同的理念,如果我们已经熟悉 React,那么学习 Solid 就会容易得多。
但有几个关键区别:
- Solid 是预编译的,与 Svelte 类似。这意味着性能提升将融入到最终版本中,因此需要发布的代码更少。
- Solid 不使用虚拟 DOM,即使组件只是函数,就像在 React 中一样,它们在首次渲染时只被调用一次。(在 React 中,只要该组件有更新,就会调用它们。
信号到底是什么?
信号基于观察者模式,这是经典的“GOF”设计模式之一。事实上,Knockout使用的东西非常像信号,称为“可观察量”。
面向对象设计原则
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
设计模式六大原则
- 单一职责原则:即一个类应该只负责一项职责
- 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 依赖倒转原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
- 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
- 迪米特法则:一个对象应该对其他对象保持最少的了解
- 开闭原则:对扩展开放,对修改关闭 信号是反应式应用中原子含量最高的部分。它们是具有初始值的可观察值,并提供可用于分别查看或更新此值的 getter 和 setter 方法。然而,为了充分利用信号,我们需要反应,即订阅信号并响应值变化而运行的效果。
当信号的值发生变化时,它有效地发出一个事件(或“信号”),然后触发反应(或“效果”)。这通常是对更新和呈现依赖于此值的任何组件的调用。据说这些组件订阅信号**。这意味着如果信号值发生变化,将仅更新这些组件。
Solid 中的一个关键概念是一切都是一种效果,甚至是视图渲染。每个信号都与其影响的特定组件紧密相连。这意味着,当值更改时,可以非常细粒度的方式重新呈现视图,而无需昂贵的整页重新呈现。
信号示例
要在 Solid 中创建信号,我们需要使用该函数并为其返回值分配两个变量,如下所示:createSignal
const [name, setName] = createSignal("Diana Prince");
这两个变量表示 getter 和 setter 方法。在上面的例子中, 是吸气手和二传手。传递给的值表示信号的初始值。name``setName``0``createSignal
对于 React 开发人员来说,这当然看起来很熟悉。使用 React 钩子创建类似内容的代码如下所示:
const [name, setName] = useState("Diana Prince");
在 React 中,getter () 的行为类似于变量,而 setter () 是一个函数。name``setName
但即使它们看起来非常相似,主要区别在于它的行为类似于 React 中的变量,而它是 Solid 中的一个函数。name
具有函数意味着,当在效果器中调用时,它会自动将该效果订阅到信号中。这意味着,当信号值发生变化时,效果将使用新值运行。name
下面是一个带有我们信号的示例:name()
createEffect(() => console.log(`Hello ${name()}`))
该函数可用于运行基于任何信号值的效果,例如将值记录到控制台。它将订阅函数内引用的任何信号。如果信号的任何值发生变化,效果码将再次运行。createEffect
在我们的示例中,如果我们使用 setter 函数更改信号的值,我们可以看到效果代码运行并且新名称被记录到控制台中:name``setName
setName("Wonder Woman")
将 getter 作为函数也意味着始终返回最新的当前值,而其他框架通常可以返回“过时”值,即使在更新后也是如此。这也意味着任何信号都可以很容易地绑定到计算值并记忆:
const nameLength = createMemo(() => name().length)
这将创建一个只读信号,可以使用 访问该信号。其值会根据信号值的任何变化而更新。nameLength()``name
如果信号包含在组件中,则组件将自动订阅此信号,并在其值更改时重新渲染:name()
import { render } from "solid-js/web"
import { createSignal } from "solid-js"
const HelloComponent = () => {
const [name, setName] = createSignal("Diana Prince");
return <h1>Hello {name()}</h1>
}
render(() => <HelloComponent />, document.getElementById("app"));
使用 更新信号的值将导致重新渲染。此外,该函数仅被调用一次以创建相关的 HTML。一旦被调用,它就不必再次运行,即使信号值有任何更新也是如此。但是,在 React 中,每当组件函数包含更改的值时,它们就会被调用。name``setName``HelloComponent``HelloComponent``name
与 Solid 的另一个主要区别是,尽管使用 JSX 作为其视图逻辑,但它根本不使用虚拟 DOM。相反,它使用现代 Vite 构建工具提前编译代码。这意味着需要交付的JavaScript要少得多,并且不需要实际的Solid库来附带它(与Svelte非常相似)。该视图是用 HTML 构建的。然后,动态进行细粒度更新 - 使用模板文本系统来识别任何更改,然后执行良好的老式 DOM 操作。
这些对 DOM 特定区域的孤立和细粒度更新与 React 在任何更改后完全重建虚拟 DOM 的方法非常不同。直接对 DOM 进行更新可以减少维护虚拟 DOM 的开销,并使其异常快速。事实上,Solid 在渲染速度方面有一些令人印象深刻的统计数据——仅次于原版 JavaScript。
角度信号
如前所述,Angular 最近采用了信号来进行细粒度更新。它们的工作方式与 Solid 中的信号类似,但创建方式略有不同。
要创建信号,请使用该函数并将初始值作为参数传递:signal
const name = signal("Diana Prince")
然后,信号被分配到的变量名称(在上面的例子中)可以用作 getter:name
console.log(name)
<< Diana Prince
信号还有一个可用于更新其值的方法,如下所示:set
name.set("Wonder Woman")
console.log(name)
<< Wonder Woman
Angular 中的细粒度更新方法与 Solid 中的方法几乎相同。首先,Angular 有一个类似于 的方法,但派生值而不是替换它:update()``set
name.update(name => name.toUpperCase())
这里唯一的区别是将值 () 作为参数并对其执行指令 ()。当 getter 被替换的最终值未知时,这非常有用,因此必须派生。name``.toUpperCase()
其次,Angular还具有创建记忆信号的功能。它的工作方式与Solid完全相同:computed()``createMemo
const nameLength = computed(() => name().length)
与 Solid 非常相似,每当检测到计算函数中的信号值已更改时,计算信号的值也会更改。
最后,Angular 具有函数,其工作原理与 Solid 中的函数完全相同。每当它所依赖的值更新时,都会重新执行副作用:effect()``createEffect()
effect(() => console.log(`Hello`, name()))
name.update(name => name.toUpperCase())
// Hello DIANA PRINCE
固体的其他特点
不仅仅是信号使 Solid 值得关注。正如我们已经指出的,它在创建和更新内容方面都非常快。它还有一个与 React 非常相似的 API,所以对于以前使用过 React 的人来说,它应该很容易上手。但是,Solid 在引擎盖下以非常不同的方式工作,并且通常性能更高。
Solid 的另一个不错的特性是它为 JSX 添加了一些漂亮的功能,例如控制流。它允许我们使用组件创建 for 循环,并且我们可以使用 .<For>``<ErrorBoundary>
此外,该组件还便于在常规流(例如模态)之外显示内容。嵌套反应性意味着对值数组或对象的任何更改都将重新呈现已更改的视图部分,而不必重新呈现整个列表。Solid 使使用品牌旗舰店更容易实现这一点。Solid 还支持服务器端渲染、开箱即用的流式传输。<Portal>
对于任何热衷于尝试 Solid 的人来说,Solid 网站上有一个很好的入门教程,我们可以在 Solid 操场上尝试代码。
结论
在本文中,我们介绍了信号的概念以及它们如何在实体和角度中使用。我们还研究了它们如何帮助 Solid 在不需要虚拟 DOM 的情况下对 DOM 执行细粒度更新。许多框架现在都在采用信号范式,所以它们绝对是一个值得我们动手的技巧!