Signals:JavaScript框架的细粒度响应性

lxf2023-05-07 00:41:45

在本文中,我们将深入探讨如何在Solid中使用signal。Solid是一种现代的响应式JavaScript库,用于构建主要依赖组件的用户界面。

Signals介绍

网络开发中的最新趋势之一是使用signal,它提供了一种更具响应性的方式来更新程序中可能会发生变化的值。当一个值被更新时,使用该值的所有内容也会被更新。这正是signal如此独特的地方。

signal的增长和人们对它们的兴趣,让人想起2019年React 16.8版本引入hooks时所引起的轰动。hooks的目的是使状态更新(最终是所有更新)在方法上更具功能性,并远离使用类。尽管signal看起来几乎与hooks相同,但有一些微妙的区别将它们区别开来(我们将在下面探讨)。

signal的使用场景: Solid库一直使用signal。 Angular最近将signal作为检测应用程序中更改的主要方式。 Preact最近引入signal作为管理状态的工具。 Vue拥有响应性API,其中refs基本上是以不同的名称表示的signal。

什么是Solid?

Solid(也称为SolidJS)由Ryan Carniato于2016年创建,于2018年发布。用他自己的话来说,它“出于对继续使用来自Knockout.js的细粒度反应模式的渴望而产生”。

当时,他不喜欢像React和Vue这样的库所采取的方向,并且“只喜欢使用比组件更小、独立的原语所带来的控制性和可组合性”。他的解决方案是创建Solid,一种使用signal创建细粒度反应性的响应式框架——这种signal模式现在也可以在许多其他框架中看到。

乍一看,Solid看起来很像使用了hooks和函数组件的React。在某些方面,这是正确的:它们在数据管理方面都有相同的哲学,如果我们已经熟悉React,那么学习Solid会更容易。

但是有一些关键的区别:

Solid是预编译的,类似于Svelte。这意味着性能收益已经包含在最终构建中,因此需要运输的代码更少。 Solid不使用虚拟DOM,即使组件像React中一样只是函数,但它们只在第一次呈现时被调用一次。(在React中,每当该组件更新时,它们都会被调用。)

Signal究竟是什么?

signal基于观察者模式,这是经典的四人帮设计模式之一。事实上,Knockout使用了与signal非常类似的东西,称为“可观察对象”。

signal是反应式应用程序中最原子的部分。它们是可观察的值,具有初始值并提供getter和setter方法,用于查看或更新该值。然而,要充分利用signal,我们需要反应,这些是订阅signal并在值更改时运行的效果。

当signal的值更改时,它实际上发出一个事件(或“signal”),然后触发一个反应(或“效果”)。这通常是调用更新并呈现依赖于此值的任何组件。这些组件称为订阅该signal。这意味着如果signal的值更改,只有这些组件将被更新。

Solid的一个关键概念是,每个东西都是效果,即使是视图呈现。每个signal都与它影响的特定组件紧密相关联。这意味着,当值发生更改时,可以以非常精细的方式重新呈现视图,而无需进行昂贵的整个页面重新呈现。

一个Signals的例子

在Solid中创建一个signal,我们需要使用createSignal函数并将其返回值分配给两个变量,如下所示:

const [name, setName] = createSignal("Diana Prince");

这两个变量表示getter和setter方法。在上面的示例中,name是getter,setName是setter。传递给createSignal的0值表示signal的初始值。

对于React开发人员来说,这当然看起来很熟悉。创建类似的内容使用React hooks的代码如下所示:

const [name, setName] = useState("Diana Prince");

在React中,getter(name)表现得像一个变量,setter(setName)则是一个函数。

但尽管它们看起来非常相似,主要的区别是在React中name表现得像一个变量,而在Solid中它是一个函数。

将name作为函数意味着,在效果内部调用它时,它会自动订阅该效果到该signal。这意味着当signal的值发生变化时,该效果将使用新值运行。

这是一个关于我们的name()signal的例子:

createEffect(() => console.log(`Hello ${name()}`))

createEffect 函数可用于基于任何signal的值运行效果,例如将一个值记录到控制台中。函数会订阅在其内部被引用的任何signal。如果任何signal的值改变,那么这个效果代码将再次运行。

在我们的示例中,如果我们使用 setName setter 函数更改 name signal的值,我们可以看到效果代码运行并将新名称记录到控制台中:

setName("Wonder Woman")

在 Solid 中,使用函数作为 getter 意味着始终返回最新的当前值,而其他框架在更新后通常会返回“过时”的值。这也意味着可以轻松地将任何signal绑定到计算值并进行记忆化:

const nameLength = createMemo(() => name().length)

这将创建一个只读signal,可以使用nameLength()访问该signal。其值会响应任何对name signal值的更改而更新。

如果将name()signal包含在组件中,则该组件将自动订阅此signal,并在其值更改时重新呈现。

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"));

更新name signal的值将导致HelloComponent被重新渲染。此外,HelloComponent函数只被调用一次来创建相关的HTML。一旦它被调用,即使名称signal的值有任何更新,它也不必再次运行。然而,在React中,组件函数会在它们包含的任何值更改时被调用。

Solid的另一个主要区别是,尽管在其视图逻辑中使用JSX,但它根本不使用虚拟DOM。相反,它使用现代的Vite构建工具预先编译代码。这意味着需要运输的JavaScript要少得多,并且不需要将实际的Solid库与之一起运输(与Svelte非常相似)。视图是用HTML构建的。然后,使用模板文字系统识别任何更改,然后执行传统的DOM操作进行实时精细化更新。

这些隔离的、精细化的更新特定于DOM区域与React的完全重建虚拟DOM的方法非常不同。直接更新DOM减少了维护虚拟DOM的开销,使其异常快速。实际上,Solid在渲染速度方面有一些令人印象深刻的统计数据,仅次于原生JavaScript。

所有基准测试结果都可以在此处查看。

Signals in Angular

如前所述,Angular 最近也采用了 Signal 来进行精细化更新。它们的工作方式与 Solid 中的 Signal 类似,但创建方式略有不同。

使用 signal 函数来创建 Signal,并将初始值作为参数传递:

const name = signal("Diana Prince")

可以使用分配给signal的变量名(例如上面的示例中的name)作为getter:

console.log(name)
<< Diana Prince

该signal还具有一个 set 方法,可用于更新其值,如下所示:

name.set("Wonder Woman")
console.log(name)
<< Wonder Woman

Angular中的细粒度更新方法与Solid几乎完全相同。首先,Angular有一个update()方法,它与set方法类似,但是派生值而不是替换它:

name.update(name => name.toUpperCase())

这里唯一的区别是将值(name)作为参数,并对其执行一个操作(.toUpperCase())。当getter被替换的最终值未知且必须导出时,这非常有用。

其次,Angular还具有computed()函数用于创建一个memoizing signal。它的工作方式与Solid的createMemo完全相同:

const nameLength = computed(() => name().length) 

最后,Angular 还有 effect() 函数,与 Solid 中的 createEffect() 函数完全相同。当它所依赖的任何值更新时,副作用函数将被重新执行:

effect(() => console.log(`Hello`, name()))

name.update(name => name.toUpperCase())
// Hello DIANA PRINCE

Solid 的其他特性

Solid 不仅仅是signal让它值得关注。正如我们已经注意到的那样,它在创建和更新内容方面非常快。它还具有与 React 非常相似的 API,因此对于已经使用过 React 的人来说,应该很容易掌握。但是,Solid 在底层工作方式上与 React 差别很大,通常更具性能。

Solid 的另一个好处是它添加了一些 JSX 的巧妙功能,例如控制流程。使用 组件,我们可以创建 for 循环,并且我们可以使用 在组件中包含错误。

此外, 组件对于在常规流之外显示内容(例如模态框)也很方便。而嵌套反应意味着对值数组或对象所做的任何更改都将重新呈现视图中已更改的部分,而不必重新呈现整个列表。使用 Stores,Solid 让这变得更加容易实现。Solid 还支持开箱即用的服务器端渲染、水合和流。

对于想要尝试 Solid 的任何人来说,Solid 网站上有一个优秀的入门教程,我们可以在 Solid Playground 中尝试代码。

结论

在本文中,我们介绍了signal的概念以及它们在Solid和Angular中的使用方式。我们还讨论了signal如何在没有虚拟DOM的情况下帮助Solid实现细粒度更新DOM。现在有许多框架正在采用signal的范式,所以学会它们绝对是一种有用的技巧

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!