百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

JS 最新提案 Signals(信号),stage 0 草案正式发布!

ccwgpt 2024-11-19 02:29 41 浏览 0 评论

今天,我十分鸡冻地和大家共享,JS Signals(信号)提案的 stage 0 草案、以及符合规范的 polyfill(功能补丁)现已正式公开发布。

大家可能已经在前端生态的某些库和框架中,或多或少听说过 Signals 了,包括但不限于:Vue、Angular、MobX、Preact、Qwik、RxJS、Solid、Svelte 等等。

举个栗子,地表最强响应式框架 —— Vue 的官方文档就专门阐释了响应式和 Signals 的“超友谊关系”:

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 A TC39 Proposal for Signals。

Signals 是什么鬼物?

Signals 是一种数据类型,它对状态单元以及从其他状态或计算值导出的计算建模,从而实现单向数据流。

状态和计算值形成一个非循环图,其中每个节点都拥有其他节点,这些节点是从其值导出状态的 sink(状态槽),或者是将状态贡献给其值的 source(状态源)。节点也可以被跟踪为“clean”(净值)或“dirty”(脏值)。

举个栗子,假设我们有一个想要追踪的计数器,我们可以将其表示为状态:

js复制代码// 计数器
const counter = new Signal.State(0)

我们可以:

  • 使用 get() 读取当前值
  • 使用 set() 更改当前值
js复制代码console.log(counter.get()) // 0

counter.set(1)
console.log(counter.get()) // 1

现在,假设我们想要另一个信号,用来表示计数器是否为偶数。

js复制代码// 是否为偶数
const isEven = new Signal.Computed(() => (counter.get() & 1) == 0)

计算值是不可写的,但我们总是可以读取其最新值:

js复制代码console.log(isEven.get()) // false
counter.set(2)
console.log(isEven.get()) // true

如上所示,isEvencounter 的状态槽,而 counterisEven 的状态源。

我们可以添加另一个计算值来实现计数器的奇偶校验:

js复制代码// 奇偶判定
const parity = new Signal.Computed(() => (isEven.get() ? 'even' : 'odd'))

现在,isEvenparity 的状态源,且 parityisEven 的状态槽。我们可以改变最初的 counter,状态会单向流向 parity

js复制代码counter.set(3)
console.log(parity.get()) // odd,奇数

目前为止我们所做的一切似乎都可以通过普通的函数组合来实现。但为何我们还需要 Signals 呢?

回想一下,上文我们提及的 Signals 可以是净值或脏值。

当我们更改 counter 的值时,它就会变成脏值。因为我们有图形关系,所以我们可以将 counter 的所有状态槽标记为脏值,以及对状态槽的所有状态槽如法炮制。

粉丝请注意,Signals 算法不是推送模型。更改 counter 不会立即推送更新 isEven 的值,然后通过图表推送更新 parity

Signals 也不是纯粹的拉取模型。读取 parity 的值并不总是计算 parityisEven 的值。相反,当 counter 更改时,它只将脏值标志中的更改推送到图表中。任何潜在的重新计算都会被延迟,直到明确提取特定 Signals 的值为止。

我们称之为“先推后拉”模型。脏值标志会被迫切更新(推送),而计算值会被延迟求值(拉动)。

将非循环图数据结构与“先推后拉”算法相结合会产生许多优点。包括但不限于:

  • Signal.Computed 会自动记忆。如果状态源不变,那就无需重新计算。
  • 即使状态源变更,也不会重新计算不需要的值。如果计算值是脏值,但没有任何内容读取其值,那就不会发生重新计算。
  • 错误或“过度更新”可以避免。举个栗子,如果我们将 counter2 改为 4,那么它是脏值。但是当我们拉取 parity 的值时,它的计算不需要重新运行,因为一旦拉取了 isEven,就会为 4 返回与 2 相同的结果。
  • 当 Signals 变脏时,我们会收到通知,并选择如何响应。

事实证明,这些特性在高效更新 UI 时至关重要。为了了解实现过程,我们可以引入一个虚构的 effect 函数,当其中一个状态源变脏时,该函数会调用某些操作。举个栗子,我们可以使用 parity 更新 DOM 中的文本节点:

js复制代码effect(() => (node.textContent = parity.get()))

counter.set(2)
counter.set(4)

Signals 提案的细节

Signals 提案的具体内容包括但不限于:

  • 背景、动机、设计目标和常见问题解答
  • 用于创建状态信号和计算信号的 API
  • 用于侦听信号的 API
  • 符合规范的polyfill,涵盖所有建议的API。
  • ......

Signals 提案不包括 effect API,因为此类 API 通常与高度依赖框架/库的渲染和批处理策略深度集成。然而,Signals 提案确实试图定义一组原语和工具函数,库作者可以使用它们来自定义专属 effect

Signals 的用户分为两大类:

  • 应用开发者
  • 库/框架/基建开发者

供应用开发者使用的 API 直接从 Signal 的命名空间暴露。其中包括 Signal.State()Signal.Computed()。API 很少在应用程序代码中使用,且更可能涉及微妙的处理,通过 Signal.subtle 命名空间暴露。其中包括 Signal.subtle.WatcherSignal.subtle.untrack() API。

应用开发者如何使用 Signals?

如今许多人气爆棚的组件和渲染框架已经在使用 Signals 了。一旦 Signals 提案成功通过,应用开发者的模式不会改变。

然而,它们的框架会:

  • 更具互操作性,因为 Signals 有官方标准
  • 体积更小,因为 Signals 是内置的,不需要 JS 框架提供
  • 性能更快,因为 Signals 会作为 JS 运行时的原生功能

使用 Signals 的一种最佳选择是和装饰器强强联手。我们可以创建一个 @signal 装饰器,将访问器转换为 Signals,如下所示:

js复制代码// 装饰器
export function signal(target) {
  const { get } = target

  return {
    get() {
      return get.call(this).get()
    },

    set(value) {
      get.call(this).set(value)
    },

    init(value) {
      return new Signal.State(value)
    }
  }
}

然后我们可以使用该装饰器来减少样板文件,并提高 Counter 类的可读性,如下所示:

js复制代码export class Counter {
  @signal accessor #value = 0

  get value() {
    return this.#value
  }

  increment() {
    this.#value++
  }

  decrement() {
    if (this.#value > 0) {
      this.#value--
    }
  }
}

库/基建开发者如何集成信号?

我们希望视图和组件库的维护者、以及创建状态管理库的维护者能够尝试集成 Signals 提案。

第一个集成步骤是更新库的信号,这样可以在内部使用 Signal.State()Signal.Computed(),而不是当前特定于库的实现。

常见的下一步是更新任何 effect 或等效基建,因为 Signals 提案没有提供 effect 的实现。我们的研究表明,effect 与渲染和批处理的细节密切相关,目前无法标准化。

相反,Signal.subtle 命名空间提供了框架可以用来构建专属 effect 的原语。

举个栗子,实现一个简单的 effect 函数,该函数对微任务队列进行批量更新。

js复制代码let needsEnqueue = true

const w = new Signal.subtle.Watcher(() => {
  if (needsEnqueue) {
    needsEnqueue = false
    queueMicrotask(processPending)
  }
})

function processPending() {
  needsEnqueue = true

  for (const s of w.getPending()) {
    s.get()
  }

  w.watch()
}

export function effect(callback) {
  let cleanup

  const computed = new Signal.Computed(() => {
    typeof cleanup === 'function' && cleanup()
    cleanup = callback()
  })

  w.watch(computed)
  computed.get()

  return () => {
    w.unwatch(computed)
    typeof cleanup === 'function' && cleanup()
  }
}

effect 函数首先根据用户提供的回调创建一个 Signal.Computed()。然后它可以使用 Signal.subtle.Watcher 来侦听计算值的状态源。

为了使侦听器能够“看到”状态源,我们需要至少执行一次计算,这可以通过调用 get() 来实现。

我们的 effect 实现支持回调的基本机制,提供清理函数以及通过返回的函数停止侦听的方法。

我们创建的 Signal.subtle.Watcher,构造函数采用一个回调,每当其监视的任何信号变脏时,都会同步调用该回调。

由于 Watcher 可以监视任意数量的 Signals,因此我们安排对微任务队列上的所有脏值进行处理。一些基本的保护逻辑确保调度能且仅能发生一次,直到处理待处理的 Signals 为止。

processPending() 函数中,我们循环侦听器跟踪的所有待处理 Signals,并通过调用 get() 重新求值,然后要求侦听器恢复监视所有跟踪的 Signals。

大多数框架将以与其渲染或组件系统集成的方式处理调度,并且它们可能会进行其他实现更改以支持其系统的工作模型。

高潮总结

接下来的几周内,我们会在 TC39 上提交 Signals 提案,探索 stage 1。stage 1 意味着 Signals 提案正在考虑中。

目前,Signals 提案仍处于并将长期处于处于 stage 0。在 TC39 技术委员会上发言后,我们会根据委员会的反馈,以及我们从 GitHub 参与者的意见继续完善 Signals 提案。

作者:前端俱乐部 链接:https://juejin.cn/post/7357382699629559845

相关推荐

腾讯开源框架TarsCpp-rpc设计分析-server(二)

2Tars协议2.1是什么借用官方说法:TARS编码协议是一种数据编解码规则,它将整形、枚举值、字符串、序列、字典、自定义结构体等数据类型按照一定的规则编码到二进制数据流中。对端接收到二进制数据流...

微服务调用为什么用RPC框架,http不更简单吗?

简单点,HTTP是协议,RPC是概念!实现RPC可以基于HTTP协议(Feign),TCP协议(Netty),RMI协议(Soap),WebService(XML—RPC)框架。传输过程中,也因为序列...

go-zero:开箱即用的微服务框架(gin框架微服务)

go-zero是一个集成了各种工程实践的Web和rpc框架,它的弹性设计保障了大并发服务端的稳定性,并且已经经过了充分的实战检验。go-zero在设计时遵循了“工具大于约定和文档”的理...

SOFARPC :高性能、高扩展性、生产级的 Java RPC 框架

#暑期创作大赛#SOFARPC是一个高性能、高扩展性、生产级的JavaRPC框架。在蚂蚁金服,SOFARPC已经使用了十多年,已经发展了五代。SOFARPC致力于简化应用程序之间的RPC...

自研分布式高性能RPC框架及服务注册中心ApiRegistry实践笔记

痛点1.bsf底层依赖springcloud,影响bsf更新springboot新版本和整体最新技术版本升级。2.eureka已经闭源,且框架设计较重,同时引入eureka会自行引入较多sprin...

Rust语言从入门到精通系列 - Tonic RPC框架入门实战

Rust语言是一种系统级语言,被誉为“没有丧失性能的安全语言”。Rust语言的优势在于其内存安全机制,在编译时就能保证程序的内存安全。Tonic模块是Rust语言的一个RPC(RemoteProce...

腾讯开源框架TarsCpp-rpc设计分析-client(一)

前言Tars是腾讯开源的微服务平台,包含了一个高性能的rpc框架和服务治理平台,TarsCpp是其C++版本。对于以C++为主要开发语言,同时还想深入了解rpc和微服务框架具体实现的同学来说,Tars...

设计了一款TPS百万级别的分布式、高性能、可扩展的RPC框架

为啥要开发RPC框架事情是这样的,在开发这个RPC框架之前,我花费了不少时间算是对Dubbo框架彻底研究透彻了。冰河在撸透了Dubbo2.x和Dubbo3.x的源码之后,本来想给大家写一个Dubbo源...

rpc框架使用教程,超级稳定好用,大厂都在使用

rpc是什么远程调用协议如何使用导入依赖<dependency><groupId>org.apache.dubbo</groupId><art...

Layui 框架实战:动态加载 Select 与二级联动全解析

在现代Web开发中,下拉选择框(Select)是用户输入数据时不可或缺的组件。很多时候,我们需要的选项并非静态写死在HTML中,而是需要根据业务逻辑从后端动态获取。更有甚者,我们可能需要实现“...

15个能为你节省数百小时的前端设计神器,从UI库到文档生成

无论你是刚开始开发之旅的新手,还是疲于应付生产期限的资深程序员,有一个真理始终不变:正确的工具能彻底改变你的工作流程。多年来,我测试了数百个开发工具——有些实用,大多数平庸。但有一批免费网站经受住了时...

Layui与WinForm通用权限管理系统全解析

嘿,小伙伴们,今天咱们来聊聊Layui和WinForm这两个框架在通用权限管理系统中的应用。别担心,我会尽量用简单易懂的语言来讲解,保证让大家都能跟上节奏!首先说说Layui。Layui是一个前端UI...

纯Python构建精美UI!MonsterUI让前端开发效率飙升

“无需CSS知识,告别类名记忆,11行代码实现专业级卡片组件”在传统Web开发中,构建美观界面需要同时掌握HTML、CSS、JavaScript三剑客,开发者不得不在多种语言间频繁切换。即使使用Boo...

WebTUI:将终端用户界面(TUI)之美带到浏览器的CSS库

在当今Web技术飞速发展的时代,界面设计愈发复杂多样。然而,随着现代化工具的广泛使用,一些开发者开始回归极简风格,追求一种简洁而富有韵味的设计。WebTUI正是这样一款CSS库,它将经典的终...

人教版二年级下册生字描红汇总(拼音+笔顺+描红),可打印!

可定制内容,评论区留言。本次整理的为人教版二年级下册所有生字,共计300个;写字是小学阶段一项重要的基本功训练,把汉字写得正确、工整、美观,可以提高运用汉字这一交际工具的准确性和效率。对小学生进行写字...

取消回复欢迎 发表评论: