作者 | jump--jump
signals 在目前前端框架的选型中遥遥领先!
国庆节前最后一周在 code review 新同学的 react 代码,发现他想通过 memo 和 usecallback 只渲染被修改的子组件部分。事实上该功能在 react 中是难以做到的。因为 react 状态变化后,会重新执行 render 函数。也就是在组件中调用 setstate 之后,整个函数将会重新执行一次。
react 本身做不到。但是基于 signals 的框架却不会这样,它通过自动状态绑定和依赖跟踪使得当前状态变化后仅仅只会重新执行用到该状态代码块。
个人当时没有过多的解释这个问题,只是匆匆解释了一下 react 的渲染机制。在这里做一个 signals 的梳理。
优势
对比 react,基于 signals 的框架状态响应粒度非常细。这里以 solid 为例:
import { createsignal, oncleanup } from solid-js;const countingcomponent = () => { // 创建一个 signal const [count, setcount] = createsignal(0); // 创建一个 signal const [count2] = createsignal(666); // 每一秒递增 1 const interval = setinterval(() => { setcount((c) => c + 1); }, 1000); // 组件销毁时清除定时器 oncleanup(() => clearinterval(interval)); return ( count: {count()} {console.log(count is, count())}
count2: {count2()} {console.log(count2 is, count2())}
);};上面这段代码在 count 单独变化时,只会打印 count,压根不会打印 count2 数据。 控制台打印如下所示:
count is 0
count2 is 666
count is 1
count is 2
...
从打印结果来看,solid 只会在最开始执行一次渲染函数,后续仅仅只会渲染更改过的 dom 节点。这在 react 中是不可能做到的,react 是基于视图驱动的,状态改变会重新执行整个渲染函数,并且 react 完全无法识别状态是如何被使用的,开发者甚至可以通过下面的代码来实现 react 的重新渲染。
const [, forcerender] = usereducer((s) => s + 1, 0);
除了更新粒度细之外,使用 signals 的框架心智模型也更加简单。其中最大的特点是:开发者完全不必在意状态在哪定义,也不在意对应状态在哪渲染。如下所示:
import { createsignal } from solid-js;// 把状态从过组件中提取出来const [count, setcount] = createsignal(0);const [count2] = createsignal(666);setinterval(() => { setcount((c) => c + 1);}, 1000);// 子组件依然可以使用 count 函数const subcountingcomponent = () => { return {count()}
;};const countingcomponent = () => { return ( count: {count()} {console.log(count is, count())}
count2: {count2()} {console.log(count2 is, count2())}
);}; 上述代码依然可以正常运行。因为它是基于状态驱动的。开发者在组件内使用 signal 是本地状态,在组件外定义 signal 就是全局状态。
signals 本身不是那么有价值,但结合派生状态以及副作用就不一样了。代码如下所示:
import { createsignal, oncleanup, creatememo, createeffect, onmount,} from solid-js;const [count, setcount] = createsignal(0);setinterval(() => { setcount((c) => c + 1);}, 1000);// 计算缓存const doublecount = creatememo(() => count() * 2);// 基于当前缓存const quadruplecount = creatememo(() => doublecount() * 2);// 副作用createeffect(() => { // 在 count 变化时重新执行 fetch fetch(`/api/${count()}`);});const countingcomponent = () => { // 挂载组件时执行 onmount(() => { console.log(start); }); // 销毁组件时执行 oncleanup(() => { console.log(end); }); return ( count value is {count()}
doublecount value is {doublecount()}
quadruplecount value is {quadruplecount()}
);};从上述代码可以看到,派生状态和副作用都不需要像 react 一样填写依赖项,同时也将副作用与生命周期分开 (代码更好阅读)。 实现机制
细粒度,高性能,同时还没有什么限制。不愧被誉为前端框架的未来。那么它究竟是如何实现的呢? 本质上,signals 是一个在访问时跟踪依赖、在变更时触发副作用的值容器。 这种基于响应性基础类型的范式在前端领域并不是一个特别新的概念:它可以追溯到十多年前的 knockout observables 和 meteor tracker 等实现。vue 的选项式 api 也是同样的原则,只不过将基础类型这部分隐藏在了对象属性背后。依靠这种范式,vue2 基本不需要优化就有非常不错的性能。
依赖收集
react usestate 返回当前状态和设置值函数,而 solid 的 createsignal 返回两个函数。即:
type usestate = (initial: any) => [state, setter];type createsignal = (initial: any) => [getter, setter];为什么 createsignal 要传递 getter 方法而不是直接传递对应的 state 值呢?这是因为框架为了具备响应能力,signal 必须要收集谁对它的值感兴趣。仅仅传递状态是无法提供 signal 任何信息的。而 getter 方法不但返回对应的数值,同时执行时创建一个订阅,以便收集所有依赖信息。 模版编译
要保证 signals 框架的高性能,就不得不结合模版编译实现该功能,框架开发者通过模版编译实现动静分离,配合依赖收集,就可以做到状态变量变化时点对点的 dom 更新。所以目前主流的 signals 框架没有使用虚拟 dom。而基于虚拟 dom 的 vue 目前依靠编译器来实现类似的优化。 下面我们先看看 solid 的模版编译:
const countingcomponent = () => { const [count, setcount] = createsignal(0); const interval = setinterval(() => { setcount((c) => c + 1); }, 1000); oncleanup(() => clearinterval(interval)); return count value is {count()}
;}; 对应编译后的的组件代码。
const _tmpl$ = /*#__pure__*/ _$template(`count value is `);const countingcomponent = () => { const [count, setcount] = createsignal(0); const interval = setinterval(() => { setcount((c) => c + 1); }, 1000); oncleanup(() => clearinterval(interval)); return (() => { const _el$ = _tmpl$(), _el$2 = _el$.firstchild; _$insert(_el$, count, null); return _el$; })();}; 执行 _tmpl$ 函数,获取对应组件的静态模版
提取组件中的 count 函数,通过 _$insert 将状态函数和对应模版位置进行绑定
调用 setcount 函数更新时,比对一下对应的 count,然后修改对应的 _el$ 对应数据
其他
大家可以看一看使用 signals 的主流框架:
vue ref
angular signals
preact signals
solid signals
qwik signals
svelte 5 (即将推出)
不过目前来看 react 团队可能不会使用 signals。
signals 性能很好,但不是编写 ui 代码的好方式
计划通过编译器来提升性能
可能会添加类似 signals 的原语
preact 作者编写了 @preact/signals-react 为 react 提供了 signals。不过个人不建议在生产环境使用。
OnRobot推出即插即用智能螺丝紧固工具Screwdriver,实现快速、简易和灵活的部署
无人机+编程孕育无限可能 中国AOPA和创客火亮相2020深圳国际无人机展
长安CX70T一款让全家出游不再犯愁的7座SUV,长安CX70T拥有了很充裕的内部驾乘空间,酷爽自驾游还远吗?
一个好消息!小米6双面屏版本取消了
国产USB转单串口芯片CH9102
前端框架的Signals有何优势?
led驱动芯片概念股一览
点胶代加工在某些行业中有着怎样的应用
维修吉时利2430源表电流严重失实
Intel停掉AI芯片Nervana NNP-T 官方解释为应客户要求
本文带你看CAN总线有哪些优点
如何实现跨平台部署嵌入式智能应用的部署?
三相交流电源有什么优势
DPDK技术原理与架构
“K鹰”监测审计平台 助力物联网“智”能飞翔
传三星正在开发一种新的“双折”折叠屏手机
吉阳智能的激光极耳切割能够攻克和解决传统模切的难题
位序列的组合与分解
牛人眼中的8K视界:三星Neo QLED 8K电视全猜想
赛卓电子科创板IPO获受理!主打位置传感器芯片,募资11亿研发车规级芯片