漫谈SolidJS和响应式框架
介绍
SolidJS是一个用于构建用户界面,简单高效、性能卓越的JavaScript库
- 性能强大
- 支持JSX,工程上相对灵活
- 接口设计贴近React
BenchMark
Count Demo
1 |
|
Palyground https://playground.solidjs.com/
为什么SolidJS可以既要又要🐶
现在大多数的框架都基本实现了数据驱动的能力。即满足以下函数要求
$UI = F(state)$
在Ajax
技术产生之后,我们一直在追求如何更加高效的去根据新数据来精准更新DOM结构
。Jquery
等工具库的产生就是在这样的背景之下。
1 |
|
数据驱动的能力让我们更加专注业务逻辑产生的State
变化,而不用考虑State
变化之后视图如何发生变化。更加具体一点,从State
变化到UI
产生变动过程,我们把他称为**响应式Reactivity**
。
SolidJS能既要又要的核心就是独特的响应式的实现
预编译和约束性的JSX
预编译通常和模板语法
一起出现,因为模板的静态可以更好的分析结构和依赖State
细节,让框架更加好的追踪到 **细粒度的变量和视图的依赖关系**
**. **Svelte
就是如此来做到所称的
外科手术一样更新 DOM
Svelte3.0
版本甚至还将预编译用到了State的生成过程,将依赖链做的更长。Vue
也是使用模板语法
,但是却没有做依赖的分析
去提供响应式,只是利用模板语法的静态性来做运行时的性能优化。一个比较突出的优化特点,就是Vue
在的循环元素的更新上性能远远超越React。
因为Vue
在DOM DIFF
过程中已经从模板预编译中已经得知了标签的确定性,减少了许多对比过程。
那么Vue
和 Svelte
不同的依赖收集方案谁更加优劣呢?将在后续的章节进行介绍。
如上我们可以看到预编译通常都是从模板语法中获得收益,** **那么**SolidJS**
是如何从JSX
中获取到响应式的特性呢?
一方面和Svelte
通过JSX
中静态的部分
来提取节点和变量的绑定关系,对于变量标签和代码块SolidJS
则会选择降级
处理,当然框架本身并不推荐使用代码块来形描述UI结构
。而是推荐使用[控制流](https://www.solidjs.com/docs/latest/api#control-flow)
来写JSX
中的逻辑。
1 |
|
No Virtual Dom
SolidJS
没有采用 Virtual DOM
的方案一个明显的好处就在大幅降低内存占用。框架不用去维护整个应用的DOM
结构,以及Diff
过程中的一些临时变量的内存占用。内存指标在移动端应用中尤为重要。这对于移动端的应用是一个重大的利好。
没有Virtual DOM
的同时也就缩短了 State
变化到视图更新的步骤和堆栈调用所需要的时间。
那为什么Vue2.x
之后也要采用了VDOM
呢,明明他已经能够获取到State
和视图模板中的细粒度绑定关系了。
其中一个很原因还是性能问题,Vue1.x
的响应式原理中大量采用了Observer``Dep``Watcher
等观察者模式元件,这些元件的创建就需要很大的成本,尤其是遇到深层次的大对象,其次元件要不断地根据组件的状态,去维护之间的关联,比如取消已经消失组件的订阅。这就要耗费大量的堆栈来做这件事情。所以细粒度的去建立绑定响应关系会遇到性能瓶颈。
在Vue2.x
中将绑定的粒度提升到组件级别,减少了Wathcer,在通过Virtual DOM Diff
完成细粒度的更新。
SolidJS
中处理信号订阅的一个性能优化方案。
1 |
|
所以SolidJS 为什么可以抛弃VDOM呢?
一方面SolidJS
已经从JSX
中获取到了节点级细粒度的绑定关系,能力上可以抛弃VDOM
直接更新视图。所以我们主要从性能上讲。
不可变性
SolidJS
将一个对象整体作为一个Signal
,这其实是借鉴了React
的思想。React Component State
作为一个整体来进行更新,所以SolidJS
也是一个单向数据流动
。
我们需要通过 setXX
方式来主动触发信号更新,相对比与Vue
为每一个变量设立Observer
还需要递归遍历属性的的方案来说,大幅度降低了内存和成本。虽然是单向数据流动
,但是“无规则限制的Hook“
在工程上和双向数据流动
也不缺少便利性。
函数式组件仅执行一次
不同于React
的函数式组件,组件状态会引起函数不断执行,SolidJS
的函数式组件仅会被执行一次,节点和响应式变量的依赖关系从一开始就确定了,通过减少创建响应式绑定关系的次数来缩减细粒度节点的性能成本。
闭包实现的状态缓存
还是回到响应式的依赖链的创建方式上来说。不同于React
使用链表和强制Hook
规则来实现的状态缓存,SolidJS
采用了闭包这样的形式去缓存一些的响应式绑定关系。一方面降低了不必要的复杂结构
和引用关系,另一方面也接触了状态和某个组件视图强绑的问题,让State
变成一个不和视图生命周期绑定的纯响应式变量。
1 |
|
“No Component”
在React``Vue``Svelte
等框架中都有组件概念,从框架设计角度上来说一个很重要的原因是为了组件给响应式依赖
找一个容器或者说是边界
。而组件的生命周期则可以来更新响应式的订阅关系。生命周期一定带来了重复的运算过程,变量进出栈,等等。这是一个重要的性能瓶颈。而SolidJS
在运行时没有组件概念,只维护了响应式依赖关系。
useMemo 真的能提高性能嘛?链接
SolidJS
选择将响应式依赖的维护交给开发者手动维护,像RX.JS
一样提供响应式的API
去建立Reactive Graph
。
延迟计算
在SolidJS
的框架中充斥了大量延迟计算用法,尤其是在组件传递属性的过程当中。由于SolidJS
不允许解构Component Props
,也使得开发者最后才回去引用父组件给到值。
VS Svelte
相对比Svelte
纯静态编译框架来说,SolidJS
在性能上也强出一头。那么强在哪里呢。
JSX连续块的分析
对于JSX中连续块的分析,可以加快创建速度和减少引用。SolidJS
Svelete
可以明显的看出Svelte
没有对于JSX
连续块的分析,没有办法使用cloneNode
来加快创建速度,同时也增加了对于无用层级的闭包引用。这是不优于SolidJS
的。
依赖收集方案
1 |
|
https://svelte.dev/examples/hello-world
这是Svelte
非常简单的的Demo,这是静态编译框架的缺点,就是依赖绑定关系的确定完全由静态分析得出。这样对于逻辑表达式中的所有变量都进行了收集。
像上面的Demo一样,虽然没有用到name
变量,但是改变name
引用依旧会引发一系列的响应,这都是在进行无效的堆栈进出。反观SolidJS
或者Vue
在运行时去收集依赖,只会收集isWorld
和disheng
这两个变量,name
变量的更改并不会去触发无效过程。
总结
SolidJS
是一个没有历史包袱的框架,大量的借鉴了各种优秀框架的经验,通过一系列的局部优化达到了工程上和性能上的最优解。这十分值得我们借鉴和学习。