npm: www.npmjs.com/package/bel… 目前核心api稳定,相关的组件库还在开发。
关于设计哲学
为何react更新要重新渲染整个组件?
solidjs:
<Show when= {data.loading}>
<div>Loading...</div>
<Show when= {data.error}>
<div>Error: {data.error}</div>
</Show>
</Show>
<Switch>
<Match when= {condition1}>
<p>Outcome 1</p>
</Match>
<Match when= {condition2}>
<p>Outcome 2</p>
</Match>
</Switch>
react可以完全使用js编写复杂的界面渲染逻辑,vue或solidjs是无法做到的。
那如何在保证性能的同时,使用js编写复杂的界面渲染逻辑?我的框架中可以把复杂逻辑缩在一个小范围内。获得这两者的好处。
react的问题
状态管理粒度较粗,开发者需手动优化避免不必要的子组件渲染;
组件生命周期和副作用管理较为复杂,易出错。
细粒度更新框架,svelte,SolidJS 通过以下方式解决这些问题:
细粒度响应式更新:采用Signal机制(如createSignal),自动追踪状态依赖关系,仅更新受影响的具体DOM节点,无需虚拟DOM diff。例如通过count()调用触发精确更新,而非重新渲染整个组件。
简化状态管理:组件仅初始化一次,状态与UI绑定通过响应式原语实现,无需手动管理渲染优化(如useCallback/useMemo),降低开发者心智负担。例如使用<Show>组件实现条件渲染,自动处理更新逻辑。
更清晰的副作用管理:通过createEffect等API显式定义副作用,避免React中useEffect依赖数组的复杂性,减少意外行为。
注:sveltejs的编译器帮助我们做了这些事情,使我们不需要手动createSignal。
虚拟dom
关于虚拟dom的性能历来有许多争论。每次更新组件都会重复生成虚拟dom,这会导致内存和cpu的开销。通过diff算法比对新旧dom会产生第二次开销。不过相比更新全部dom,虚拟dom的性能要好很多。
solidjs使用细粒度更新,理论上,可以精准追踪数据变化和视图变化,精确的更改部分dom,不需要进行diff。但实际情况更加复杂。以列表为例。
solidjs:
<For each= {data()}>
{(item, index) =>
// rendering logic for each element
}
</For>
列表更新如何做到细粒度,第一种方法是使用js proxy,简单来说,他可以劫持对数组的修改,告诉你数组的哪些内容更新了,但实际上一些常见情况如sort()排序数组后,proxy会告诉你列表的所有内容都更新了,很多情况下仍需要diff以保证渲染的性能。
不可变数据结构
不可变数组
react项目通常使用immerjs或immutablejs,但他们的算法并不好,我的框架中的不可变数组实现要快的多,使用也更加简单。对于超大的数组,进行一些随机插入或删除,我的实现性能甚至优于原生js数组。
不可变对象
js引擎对于对象有很多优化,基本上大多数对象并不会用map存储,而是只存储值不存储key(类似c、c++等底层语言的struct,可以显著节约内存),对于同类型对象只会有一个对应的key map。实际上只有当对象频繁增加属性或满足一些其他条件时才会转为map。用不可变map实现js的不可变对象(如immutablejs)性能和内存占用通常会比简单复制整个对象更慢。
关于性能测试
对于前端框架的许多性能测试偏离了实际使用情况。一些常见情形也并未考虑。性能测试是非常复杂的问题。不同框架在不同场景性能不同。在考虑项目中使用使用什么框架,应优先考虑该框架是否符合项目特点,是否易于开发。堆屎山代码的项目,使用什么框架对性能的影响不大。