本文深入探讨如何在Solid中使用信号,Solid是一个现代化的、响应式的JavaScript库,主要依靠组件构建用户界面。
内容:
要点:
信号简介
Web开发的最新趋势之一是使用信号,它提供了一种更具响应性的方法来更新程序中易于变化的值。当值更新时,所有使用该值的内容也会更新。这就是信号如此独特的原因。
信号的增长及其关注度让人联想起2019年React 16.8版本发布时引起的所有轰动,当时React团队引入了钩子。钩子的目标是使状态更新(最终所有更新)更具函数式方法,并远离使用类。虽然信号看起来几乎与钩子相同,但它们之间存在一些细微的差别(我们在下面探讨)。
何为Solid?
Solid(也称为SolidJS)由Ryan Carniato于2016年创建,并于2018年发布。用他自己的话说,“它源于继续使用我从Knockout.js中喜爱的细粒度反应式模式的愿望。”
他当时不喜欢React和Vue等库的发展方向,“只是更喜欢使用比组件更小、更独立的基元所带来的控制和可组合性。”他的解决方案是创建Solid,这是一个使用信号创建细粒度响应式(一种现在也可以在许多其他框架中看到的信号模式)的响应式框架。
乍一看,Solid与带有钩子和函数式组件的React非常相似。在某些方面,这是正确的:它们在管理数据方面都具有相同的理念,如果我们已经熟悉React,这使得学习Solid更容易。
但是,也有一些关键区别:
信号究竟是什么?
信号基于观察者模式,这是经典的四人帮设计模式之一。事实上,Knockout使用与信号非常相似的东西,称为“可观察对象”。
信号是响应式应用程序中最原子化的部分。它们是可以观察到的值,具有初始值,并提供getter和setter方法,分别用于查看或更新此值。但是,为了充分利用信号,我们需要反应,它们是订阅信号并响应值变化而运行的效果。
当信号的值发生变化时,它实际上会发出一个事件(或“信号”),然后触发一个反应(或“效果”)。这通常是对更新和渲染任何依赖于此值的组件的调用。这些组件被称为订阅信号。这意味着如果信号的值发生变化,只有这些组件才会更新。
Solid中的一个关键概念是一切都是一个效果,甚至是视图渲染。每个信号都与它影响的特定组件紧密相连。这意味着,当值发生变化时,可以以非常细粒度的方式重新渲染视图,而无需昂贵的全页面重新渲染。
信号示例
要在Solid中创建信号,我们需要使用createSignal函数并将两个变量赋值给它的返回值,如下所示:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>
这两个变量代表getter和setter方法。在上面的示例中,name是getter,setName是setter。传递给createSignal的0值表示信号的初始值。
对于React开发人员来说,这当然看起来很熟悉。使用React钩子创建类似内容的代码如下所示:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
在React中,getter(name)的行为类似于变量,而setter(setName)是一个函数。
但是,即使它们看起来非常相似,主要区别在于name在React中表现得像一个变量,而在Solid中它是一个函数。
将name作为函数意味着,当在效果内部调用时,它会自动将该效果订阅到信号。这意味着,当信号的值发生变化时,效果将使用新值运行。
以下是一个使用name()信号的示例:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>
createEffect函数可用于运行基于任何信号值的效果,例如将值记录到控制台。它将订阅函数内部引用的任何信号。如果任何信号的值发生变化,则效果代码将再次运行。
在我们的示例中,如果我们使用setName setter函数更改name信号的值,我们可以看到效果代码运行并记录新的名称到控制台:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
将getter作为函数也意味着始终返回最新的当前值,而其他框架即使在值更新后也可能返回“陈旧”的值。它还意味着任何信号都可以轻松地绑定到计算值和记忆化:
<code class="language-javascript">createEffect(() => console.log(`Hello ${name()}`))</code>
这将创建一个只读信号,可以使用nameLength()访问。它的值会响应name信号值的任何更改而更新。
如果name()信号包含在组件中,则组件将自动订阅此信号,并在其值更改时重新渲染:
<code class="language-javascript">setName("Wonder Woman")</code>
使用setName更新name信号的值将导致HelloComponent重新渲染。此外,HelloComponent函数只调用一次以创建相关的HTML。一旦它被调用,它就不需要再次运行,即使name信号的值有任何更新。但是,在React中,组件函数在它们包含的值发生任何更改时都会被调用。
Solid的另一个主要区别是,尽管它使用JSX进行视图逻辑,但它根本不使用虚拟DOM。相反,它使用现代Vite构建工具预先编译代码。这意味着需要交付的JavaScript更少,并且不需要实际的Solid库与它一起交付(与Svelte非常相似)。视图是用HTML构建的。然后,使用模板文字系统来识别任何更改,然后执行良好的旧式DOM操作,从而动态进行细粒度的更新。
这些对DOM特定区域的隔离和细粒度的更新与React在任何更改后完全重建虚拟DOM的方法大相径庭。直接对DOM进行更新减少了维护虚拟DOM的开销,并使其速度异常快。事实上,Solid在渲染速度方面有一些令人印象深刻的数据——仅次于原生JavaScript。
所有基准测试都可以在这里查看。
Angular中的信号
如前所述,Angular最近采用了信号来进行细粒度的更新。它们的工作方式与Solid中的信号类似,但创建方式略有不同。
要创建信号,可以使用signal函数并将初始值作为参数传递:
<code class="language-javascript">const nameLength = createMemo(() => name().length)</code>
然后可以将分配给信号的变量名称(在上面的示例中为name)用作getter:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>
该信号还具有一个set方法,可用于更新其值,如下所示:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
Angular中细粒度的更新方法与Solid中的方法几乎相同。首先,Angular有一个update()方法,其作用类似于set,但派生值而不是替换值:
<code class="language-javascript">createEffect(() => console.log(`Hello ${name()}`))</code>
这里的唯一区别是将值(name)作为参数并对其执行指令(.toUpperCase())。当getter被替换的最终值未知并且必须派生时,这非常有用。
其次,Angular还具有computed()函数,用于创建记忆化信号。它的工作方式与Solid的createMemo完全相同:
<code class="language-javascript">setName("Wonder Woman")</code>
与Solid一样,每当检测到计算函数中信号的值发生更改时,computed信号的值也会更改。
最后,Angular具有effect()函数,其作用与Solid中的createEffect()函数完全相同。每当它依赖的值更新时,都会重新执行副作用:
<code class="language-javascript">const nameLength = createMemo(() => name().length)</code>
Solid的其他特性
并非只有信号才使Solid值得关注。正如我们已经提到的,它在创建和更新内容方面都非常快。它还具有与React非常相似的API,因此对于以前使用过React的任何人来说,它都应该很容易上手。但是,Solid在底层的工作方式非常不同,通常性能更高。
Solid的另一个不错的特性是它为JSX添加了一些巧妙的特性,例如控制流。它允许我们使用
此外,
对于任何想要尝试Solid的人来说,Solid网站上有一个优秀的入门教程,我们可以在Solid playground中试验代码。
结论
在本文中,我们介绍了信号的概念以及它们在Solid和Angular中的使用方法。我们还研究了它们如何帮助Solid在无需虚拟DOM的情况下对DOM进行细粒度的更新。许多框架现在都采用了信号范例,因此它们绝对是我们应该掌握的技巧!
关于JavaScript中信号的常见问题解答(FAQ)
JavaScript中的传统事件处理涉及将事件侦听器附加到DOM元素并响应用户交互或系统事件。但是,这种方法在大型应用程序中可能会变得复杂且难以管理。另一方面,信号为响应性提供了更细粒度的方法。它们允许您创建独立的反应行为单元,这些单元可以组合在一起以创建复杂的交互。与事件侦听器不同,信号不与任何特定的DOM元素或事件类型绑定,这使得它们更灵活,并且更容易在应用程序的不同部分重复使用。
信号的设计目的是高效且高性能。它们使用基于拉取的反应性模型,这意味着它们只在需要时计算其值。这可以导致在具有复杂反应行为的应用程序中显着提高性能,因为可以避免不必要的计算。但是,确切的性能差异将取决于具体的用例以及反应性模型的优化程度。
是的,信号可以与其他JavaScript框架(如React或Vue)一起使用。它们可以集成到这些框架的反应性系统中,以提供对反应行为的额外灵活性和控制。但是,需要注意的是,每个框架都有自己处理反应性的方式,因此您需要了解信号如何融入该模型。
调试使用信号的JavaScript应用程序可能与调试传统的事件驱动应用程序略有不同。由于信号不与特定的事件或DOM元素绑定,因此您需要跟踪数据在信号及其依赖项中的流动。 Chrome DevTools等工具对此很有帮助,因为它们允许您逐步执行代码并在每一步检查信号的状态。
虽然信号提供了许多好处,但它们也有一些局限性。一个潜在的缺点是它们可能会使您的代码更复杂,更难以理解,特别是对于不熟悉反应性概念的开发人员而言。此外,由于信号是JavaScript中相对较新的概念,与更成熟的模式和框架相比,可用的资源和社区支持可能更少。
可以使用与任何其他JavaScript应用程序相同的技术来测试使用信号的JavaScript应用程序。您可以使用单元测试来测试单个信号及其行为,并使用集成测试来测试信号如何相互交互以及与应用程序的其他部分交互。 Jest或Mocha等工具可用于此目的。
是的,信号可以在Node.js环境中使用。它们不与浏览器或DOM绑定,因此可以在任何JavaScript环境中使用。这使得它们成为在服务器端应用程序中构建反应行为的强大工具。
处理使用信号的JavaScript应用程序中的错误的方法与任何其他JavaScript应用程序类似。您可以使用try/catch块来捕获错误并适当地处理它们。此外,您可以使用错误信号来通过信号图传播错误并在集中式方式处理它们。
是的,信号可以用来构建实时应用程序。它们提供了一种强大的方法来管理和响应实时数据更新,这使得它们成为聊天应用程序、实时仪表板和多人游戏等应用程序的绝佳选择。
优化使用信号的JavaScript应用程序的性能涉及仔细管理信号依赖项并避免不必要的计算。您可以使用Chrome DevTools等工具来分析您的应用程序并识别性能瓶颈。此外,您可以使用记忆化和延迟评估等技术来提高信号的性能。
以上是信号:JavaScript框架的细粒反应性的详细内容。更多信息请关注PHP中文网其他相关文章!