Redian新闻
>
聊聊前端框架的未来Signals

聊聊前端框架的未来Signals

公众号新闻

来源 | OSCHINA 社区

作者 | jump--jump

原文链接:https://my.oschina.net/wsafight/blog/10115779

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 (
<div>
<div>
count: {count()}
{console.log("count is", count())}
</div>
<div>
count2: {count2()}
{console.log("count2 is", count2())}
</div>
</div>

);
};
上面这段代码在 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 <div>{count()}</div>;
};

const CountingComponent = () => {
return (
<div>
<div>
count: {count()}
{console.log("count is", count())}
</div>
<div>
count2: {count2()}
{console.log("count2 is", count2())}
</div>
<SubCountingComponent />
</div>

);
};

上述代码依然可以正常运行。因为它是基于状态驱动的。开发者在组件内使用 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 (
<div>
<div>Count value is {count()}</div>
<div>doubleCount value is {doubleCount()}</div>
<div>quadrupleCount value is {quadrupleCount()}</div>
</div>

);
};
从上述代码可以看到,派生状态和副作用都不需要像 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 <div>Count value is {count()}</div>;
};

对应编译后的的组件代码。

const _tmpl$ = /*#__PURE__*/ _$template(`<div>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。不过个人不建议在生产环境使用。

参考资料

  • 精读《SolidJS》:https://juejin.cn/post/7137100589208436743?searchId=2023100323265799EF4CF92C95049F6276

  • Solid.js:https://www.solidjs.com/

  • Introducing runes:https://svelte.dev/blog/runes

往期推荐



TypeScript刚刚流行起来,为什么大牛们就开始抛弃了?
Python 3.12正式发布:性能提升、no-GIL将在3.13提供
英特尔将OpenJDK的数据排序速度提高7-15倍




这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
大美和二美(二十四) - 卢卡吃香了这被绑架的天才,一上线就9.2前端根本不需要构建!“技术邪教” Ruby on Rails 之父再出激进言论引争议无法反驳,Java 最强后端框架!艺华境 初音未来stylist:黑丝+软胶可捏的Miku才200多?!“半夜聊前任被舍友劝不要离世?”哈哈这耳背别太荒谬!哈玛斯撕票!画面曝:尸体特征相符!被绑架的华人混血女生画面求救!爸爸看视频崩溃...‘Chang An’ Reignites Ancient Rivalry Between 2 Chinese Capitals8名被亲妈绑架的儿童,在北加获救被焦虑绑架的“天性”,终于在徒步里得到自由尼日利亚2021年被绑架的121名学生最后一名成功逃脱“年轻人发疯行为有多离谱?”很担心打架的精神状态...在缺乏未来感的西安谈经济的未来,意味什么?爱因斯坦不知道到的是什么?Lyft在加州3城推出性别匹配机制,Girls drive girls被「性格好」绑架的 11 个时刻时隔5年,苹果悄悄上架的这支笔把我看不会了。破局!广州市委书记:南沙是广州的未来,也是大湾区的未来!Keras 3.0正式发布!一统TF/PyTorch/Jax三大后端框架,网友:改变游戏规则灌水:the awe effect扔掉okhttp、httpClient,来试试这款轻量级 HTTP 客户端框架,吹爆!人人拥有一只「大白」的未来离我们还有多远?聊聊人形机器人的未来|极客周末小米一开源项目被批“三无”,项目导师回应;Ruby on Rails之父将TypeScript从Turbo框架中移除 | Q资讯“爸妈吵架的尽头永远是我”?热搜之下,隐藏着多少家庭的悲剧…Python Web框架的三强之争:Flask、Django和FastAPI中国SaaS:与其讨论有没有未来,不如聊聊如何走向未来蔚来选的未来还有未来吗飞临美丽的耶路撒冷圣城狂揽10k star,微软AutoGen框架太火了,智能体聊聊天就把问题解决了吵架的时候不要随便用手指人兰蔻6折!Essentials罕见半价!AllSaints吐血3折!SW靴子/Myprotein蛋白粉2折起 !最好的 Go 框架就是不用框架?《湖天一览楼》1部2章(3)庚子国变牛津词典居然收录了这些中式英语?一起来see see!功率半导体,迎来SiC时代
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。