Signal:更多前端框架的选择

开发 前端
Signal的技术在10年前Knockout框架中就有应用。为什么这项技术正受到越来越多前端框架的青睐?

Signal:更多前端框架的选择

大家好,我卡颂。

最近,Angular​、Qwik​的作者「MIŠKO HEVERY」发文表示Signal是前端框架的未来[1],并考虑在Angular中实现它。

在此之前,Vue​、Solid.js​、Preact​、Svelte​都已实现Signal​。实际上,signal并不是一个新概念,他还有很多别名,比如:

  •  响应式更新
  •  细粒度更新

如果你了解过Vue​响应式更新的实现原理,对Signal就不会陌生。

实际上,Signal​的技术在10年前Knockout框架中就有应用。为什么这项技术正受到越来越多前端框架的青睐?

本文,让我们一起探讨下这个话题。

signal的本质

signal​的本质,是将「对状态的引用」以及「对状态值的获取」分离开。这么说可能有点抽象,让我们先看一个非signal的例子。

以下是React中定义状态的方式:

function App() {
const [state, dispatch] = useState(0);
return <p onClick={
() => dispatch(state + 1)
}>{state}</p>
}

useState的返回值包括两部分:

  • state:状态的值
  • dispatch​:状态的setter

可以发现,state耦合了「对状态的引用」以及「对状态值的获取」这两个含义。

再来看一个signal​的例子。以下是同一个例子用Solid.js书写的样子:

function App() {
const [getState, dispatch] = createSignal(0);
return <p onClick={
() => dispatch(getState() + 1)
}>{getState()}</p>
}

createSignal的返回值包括两部分:

  •  getState:对状态的引用
  •  dispatch:状态的setter

区别就体现在getState上,其中:

  • getState是对状态的引用
  • getState()是对状态值的获取

也就是说,我们可以不必立刻获取状态的值,而是在需要的时候再获取(即在需要时再执行getState())。

这么做有什么好处呢?如果我们在需要的时候再获取状态的值,就能感知当前的上下文环境。

举个很粗糙的例子,在下面的代码中,组件实例(Component实例)在render时会将全局变量cpnContext指向自己:

let cpnContext = null;

class Component {
render() {
cpnContext = this;
// ...省略逻辑
}
}

那么在createSignal返回的getState方法内部,可以获取全局变量cpnContext来感知当前处于哪个组件的渲染流程:

function createSignal() {
// ...省略逻辑
function getState() {
const curContext = cpnContext;
// ...
}
function dispatch {}
return [getState, dispatch]
}

这么做的目的是建立「状态变化」与「需要更新哪个组件」之间的联系。

相比于React​,基于Signal实现的框架会有两个优势:

  •  更好的细粒度更新性能
  •  更好的DX(开发者体验)

更好的细粒度更新性能

由于Signal建立了状态与组件之间的联系,所以相比于React更有性能优势。

比如,在我的电脑上,用Svelte渲染1w个li,点击某个li后改变他的内容:

<ul>
{#each items as item (item.id)}
<li on:click={() => items[item.id].name = 'change!'}>{item.name}</li>
{/each}
</ul>

从点击事件触发,到Svelte逻辑运行完,再到浏览器重排重绘,总用时18.88ms,其中Svelte的逻辑执行只花了9.5ms:

图片

同样的例子用React实现,触发点击后总用时98.5ms,其中React的逻辑执行了89.38ms:

图片

在这个例子中,React​性能比Svelte差了一个数量级。之所以会有这样的差异,很大一部分原因在于「Svlete在更新前就知道状态变化时需要更新哪个组件」。

而这一切的源头就在于Signal。

更好的DX

更好的开发者体验主要体现在两方面:

1、Signal感知上下文环境的能力减少了代码心智负担。

比如在React中,useEffect在使用时需要指明依赖的状态:

useEffect(() => {
// ...state1, state2变化后的逻辑
}, [state1, state2])

如果采用Signal的实现,状态能感知到自己在useEffect上下文环境,可以自动建立两者之间的联系,不用再担心少写依赖状态、闭包陷阱等问题,减少心智负担。

比如在Vue中,类似useEffect(仅仅是功能类似,两者的用途其实是不同的)的watch,就不需要显式指明依赖:

<script setup>
import { ref, watch } from 'vue'

const name = ref('')

watch(name, (newName, oldName) => {
// ...省略逻辑
})
</script>

2、减少开发者性能优化的心智负担

使用Signal的框架通常能获得不错的运行时性能,所以不需要额外的性能优化API。反观React,开发者如果遇到性能问题,需要手动调用性能优化API(比如React.memo、useMemo、PureComponent)。

总结

有以上这么多优点,难怪很多框架都使用了Signal​。那么React对Signal是什么态度呢?

React团队成员对此的观点是:

  1.  有可能引入类似Signal的原语
  2. Signal​性能确实好,但他不太符合React的理念​

图片

React的理念可以用一句话概括:「UI反映状态在某一刻的快照」。

既然是快照,那就不是局部的,而是个整体概念。在React​中,状态更新会引起整个应用重新render​,就是对React快照理念的最好诠释。

React​现阶段的所有实现都是基于快照理念。所以,即使引入类似Signal​的原语,可能也是类似Mobx这样的上层实现,而不是从底层重构。

我个人比较倾向于认为:React​团队承认Signal的优点,但由于积重难返,而且现代设备的性能通常是过剩的,所以性能问题并不是首要问题。

如果这个观点是正确的,那么React​可能会在开发者体验(Signal的另一个优点)方面努努力。比如去年提出的RFC: useEvent[2]可能就是这方面的一次尝试。

参考资料

[1]Signal是前端框架的未来:https://www.builder.io/blog/usesignal-is-the-future-of-web-frameworks#code-use-ref-code-does-not-render。

[2]RFC: useEvent:https://github.com/reactjs/rfcs/pull/220。

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2021-12-06 15:35:01

CSS前端开发

2015-04-17 15:56:15

戴尔云计算

2013-10-24 10:40:23

前端框架

2023-02-20 08:41:08

SignaluseState()

2015-09-25 10:23:06

SDN

2010-05-19 14:38:11

Web开发框架Web层

2022-01-06 22:04:03

JavaScript语言开发

2018-06-10 15:45:40

物联网IoT工业

2016-07-06 11:13:02

云计算

2015-07-13 13:47:34

以太网标准带宽

2010-08-05 14:32:16

Flex框架

2019-07-17 22:07:14

前端开发框架

2013-01-16 11:35:05

Latitude 10平板电脑

2020-09-08 10:28:28

云计算云平台

2010-05-24 13:14:16

开放思杰云计算

2020-04-14 15:54:07

5G物联网Wi-Fi

2010-04-15 04:05:25

惠普UNIXTCO

2019-01-31 11:11:30

前端开发框架

2022-07-27 10:36:13

前端UI框架

2022-09-07 21:31:19

微前端架构iframe
点赞
收藏

51CTO技术栈公众号