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
如上所示,isEven 是 counter 的状态槽,而 counter 是 isEven 的状态源。
我们可以添加另一个计算值来实现计数器的奇偶校验:
js复制代码// 奇偶判定
const parity = new Signal.Computed(() => (isEven.get() ? 'even' : 'odd'))
现在,isEven 是 parity 的状态源,且 parity 是 isEven 的状态槽。我们可以改变最初的 counter,状态会单向流向 parity。
js复制代码counter.set(3)
console.log(parity.get()) // odd,奇数
目前为止我们所做的一切似乎都可以通过普通的函数组合来实现。但为何我们还需要 Signals 呢?
回想一下,上文我们提及的 Signals 可以是净值或脏值。
当我们更改 counter 的值时,它就会变成脏值。因为我们有图形关系,所以我们可以将 counter 的所有状态槽标记为脏值,以及对状态槽的所有状态槽如法炮制。
粉丝请注意,Signals 算法不是推送模型。更改 counter 不会立即推送更新 isEven 的值,然后通过图表推送更新 parity。
Signals 也不是纯粹的拉取模型。读取 parity 的值并不总是计算 parity 或 isEven 的值。相反,当 counter 更改时,它只将脏值标志中的更改推送到图表中。任何潜在的重新计算都会被延迟,直到明确提取特定 Signals 的值为止。
我们称之为“先推后拉”模型。脏值标志会被迫切更新(推送),而计算值会被延迟求值(拉动)。
将非循环图数据结构与“先推后拉”算法相结合会产生许多优点。包括但不限于:
- Signal.Computed 会自动记忆。如果状态源不变,那就无需重新计算。
- 即使状态源变更,也不会重新计算不需要的值。如果计算值是脏值,但没有任何内容读取其值,那就不会发生重新计算。
- 错误或“过度更新”可以避免。举个栗子,如果我们将 counter 从 2 改为 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.Watcher 和 Signal.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个;写字是小学阶段一项重要的基本功训练,把汉字写得正确、工整、美观,可以提高运用汉字这一交际工具的准确性和效率。对小学生进行写字...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 腾讯开源框架TarsCpp-rpc设计分析-server(二)
- 微服务调用为什么用RPC框架,http不更简单吗?
- go-zero:开箱即用的微服务框架(gin框架微服务)
- SOFARPC :高性能、高扩展性、生产级的 Java RPC 框架
- 自研分布式高性能RPC框架及服务注册中心ApiRegistry实践笔记
- Rust语言从入门到精通系列 - Tonic RPC框架入门实战
- 腾讯开源框架TarsCpp-rpc设计分析-client(一)
- 设计了一款TPS百万级别的分布式、高性能、可扩展的RPC框架
- rpc框架使用教程,超级稳定好用,大厂都在使用
- Layui 框架实战:动态加载 Select 与二级联动全解析
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)