本文深入解析SolidJS框架,探讨其通过预编译和响应式系统实现的高性能。对比React与Vue,分析SolidJS在节点级更新、无虚拟DOM机制下的优势,并展示基于Vite和TypeScript的实战开发流程。
前言
SolidJS 是一个结合了预编译技术、摒弃虚拟DOM、性能卓越的 JavaScript 框架,常被视为 React 的强力竞争者。
![]() | ![]() |
背景
近期,产品部门提出了一项临时需求,要求开发一个独立部署的首页可视化页面。目前我们的系统支持通过 iframe 嵌入方式,让用户自行选择 URL 来配置页面,并将其作为应用的一个路由页面使用。
考虑到只需单独部署一个页面,使用 React 或 Vue 显得过于沉重,简单绘制页面即可满足需求,而 jq 我已不再使用。
鉴于近期社区涌现出不少有趣的框架,之前我关注过 svelte,觉得它很有趣且符合我的使用场景,正准备尝试。
随后,在GitHub上发现了solidjs这一项目,粗略浏览后不禁感叹,这表现简直比react还要react😆~
查阅文档并编写 demo 后,发现其上手简单。通过对比测试和博客介绍,确认其性能强劲,与 svelte 一样采用预编译且无运行时开销,构建产物仅十几 KB,接近原生 JS 性能,令人惊叹。
对于此类小型项目,SolidJS 非常适用。
介绍
官方定义:SolidJS 是一个用于构建用户界面的声明式、高效且灵活的 JavaScript 框架。
Solid 采用了类似 Svelte 的预编译机制,语法风格接近 React,使用 JSX 语法和极其相似的 API。但与 React 不同,SolidJS 组件仅初始化一次,并非在 state 改变时重新渲染整个组件。这类似于 Vue3 的 setup 和响应式更新机制(更新粒度达到节点级别)。
官方给出的核心理由包括:
- 高性能 - 在公认的
UI速度和内存利用率基准测试中始终名列前茅。 - 强大 - 可组合的反应式原语与
JSX的灵活性相结合。 - 务实 - 合理且量身定制的
API使开发过程既有趣又简单。 - 生产力 - 人体工程学和熟悉感使构建简单或复杂的应用变得轻而易举。
主要优势
- 高性能 - 接近原生性能,在 js-framework-benchmark 排名中名列前茅。
- 极小的打包体积 - 编译为直接的
DOM操作,无虚拟DOM,运行时极小(类似于Svelte),适合打包为独立的webComponent嵌入其他应用。 - 易于使用 - 提供近似
React的使用体验,便于快速上手。
对比分析
我们将关注点聚焦于是否使用虚拟 DOM,以及数据的响应处理方式。
虚拟 DOM 的分析
首先,虚拟 DOM 并不一定比原生性能更好或更快。抛开真实场景谈性能都是不准确的,框架的设计和应用场景各有考量。
在状态与 DOM 操作之间抽象出一层虚拟 DOM,需要牺牲一定的运行时性能,并不一定比直接操作原生 DOM 快,具体情况具体分析,毕竟 diff 机制并非免费午餐。
- 无论数据变化多少,每次重绘的性能通常是可以接受的(提供及格的表现)。
- 你依然可以使用类似
innerHTML的思路来编写应用。 - 最重要的一点是,它实现了跨平台能力。
例如
react,对于 Web 端渲染可以使用react-dom,对于原生渲染可以使用react-native以及服务端渲染等。它们的开发模式非常类似,遵循react的语法规则即可,但在render层,只要符合react api规范,你可以提供各种不同的render渲染函数,从而实现跨平台渲染。
核心原理的选择
以我们熟悉的 react 和 vue 为例进行说明:
- React 对数据的处理是不可变(
immutable)的:具体表现为整树更新,更新时不关注具体哪个状态变化,只要有状态改变,直接进行整树 diff 找出差异并对应更新。 - Vue 对数据的处理是响应式、可变(
mutable)的:更新时,能够精确知道哪些状态发生了改变,能够实现精确到节点级别的更新(类似框架还有 Svelte、SolidJS)。
更新粒度的选择
- 应用级:有状态改变,就更新整个应用,生成新的虚拟 DOM 树,与旧树进行
Diff(代表作:React,当然,现在它的虚拟 DOM 已升级为了Fiber架构)。 - 组件级:与上方类似,只不过粒度小了一个等级(代表作:
vuev2及之后的版本)。 - 节点级:状态更新直接与具体的更新节点操作绑定(代表作
vue1.x、Svelte、SolidJS)。
在
vue1.x时代,对于数据是每个生成一个对应的Wather,更新颗粒度为节点级别,但这样创建大量的Wather会造成极大的性能开销,因此在vue2.x时代,通过引入虚拟 DOM 优化响应,做到了组件级颗粒度的更新。而对于
react来说,虚拟 DOM 就是至关重要的部分,甚至是核心。我们已经了解react属于应用级别的更新,因此整个 DOM 树的更新开销是极大的,所以这里对于虚拟DOM+diff算法的使用就是极其必要的。包括现在的fiber架构与可中断更新,也算是对虚拟 DOM 的极致压榨。
是否采用虚拟 DOM
这个选择是与上边采用何种粒度的更新设计紧密相关的:
- 是:对应用级的这种更新粒度,虚拟 Dom 简直是必需品,因为在
diff前它并不能得到此次更新的具体节点信息,必须要通过随后的虚拟Dom+Diff算法筛选出最小差异,不然整树append对性能是灾难(代表框架:React、vue)。- 但这里值得注意的事是:本质上
vue并不需要虚拟DOM,因为它这种基于依赖收集的响应式机制可以直接进行节点级更新,但vue借助虚拟DOM的抽象能力,可以做到更新粒度的随意调整(目前是组件级),给 vue 的发展提供更多可能性,尤其在跨平台渲染方面,这点十分关键。
- 但这里值得注意的事是:本质上
- 否:对节点级更新粒度的框架来说,一般没有必要采用虚拟 dom(代表作:
vue1.x、Svelte、SolidJS)。
开发语法 DSL 选择
- JSX:
React、SolidJS - 模版 + 编译指令:
vue(JSX 可选)、Svelte
正文
基本使用
import { render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
const CountingComponent = () => {
const [count, setCount] = createSignal(0);
createEffect(() => console.log('count', count()));
const handleAdd = () => {
setCount((prev) => prev + 1);
};
return <div onClick={handleAdd}>Count value is {count()}</div>;
};
render(() => <CountingComponent />, document.getElementById('app'));这简直是 React hooks 的双胞胎兄弟...。
而且因为 SolidJS 这种后发优势,没有 React 沉重的历史包袱,比如不需要处理类组件的兼容(SolidJS 只支持函数式)。这让它在实现了大部分 React 功能特性的前提下,源码体积要比 React 小很多,这让它在首屏加载方面就首先占据上风。直接调用编译好的 DOM 操作方法,省去了虚拟 DOM 比较这一步所消耗的时间,整个更新链路相比 React 变得简洁许多。
调用栈分析:


简单分析:
- 组件函数只会在整个应用生命周期里调用一次。
- 心智模型与 react 完全不一样,反而与 vue3 保持了一致,可以说兼具了
React hooks+vue3的优点。 createEffect自动追踪依赖,不需要像 react 那样维护一个dep数组。hook调用顺序没要求,以函数调用的方式解决Proxy目标必须是对象的问题。
它的响应式实现确实是与 vue 一样,都是基于发布订阅的依赖收集去做的,但它没有采用 vue 虚拟 Dom 的运行时 diff,而是充分在编译阶段做文章,将状态更新编译为独立的 DOM 操作方法。
编译内容分析
import { render, createComponent, delegateEvents, insert, template } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
const _tmpl$ = /*#__PURE__*/template(`<div>Count value is </div>`, 2);
const CountingComponent = () => {
const [count, setCount] = createSignal(0);
createEffect(() => console.log('count', count()));
const handleAdd = () => {
setCount(prev => prev + 1);
};
return (() => {
const _el$ = _tmpl$.cloneNode(true);
_el$.firstChild;
_el$.$$click = handleAdd;
insert(_el$, count, null);
return _el$;
})();
};
render(() => createComponent(CountingComponent, {}), document.getElementById('app'));
delegateEvents(["click"]);可以看到,跟基于 Virtual DOM 的框架相比,这样的输出不需要 Virtual DOM 的 diff/patch 操作,自然可以省去大量的运行时代码。而是使用了 solid-js/web 库提供的 insert 等 DOM 函数操作。
再结合以后的 webcomponent 考虑下,真是大有可为,发展空间很大,未来可期~
项目实战
直接按照官方文档示例,创建一个支持 TypeScript 的基础项目,模板默认使用 vite 构建(solidjs-templates)。
# Typescript template
$ npx degit solidjs/templates/ts my-solid-project
$ cd my-solid-project
$ npm install # or pnpm install or yarn install在 vite 中引入插件。
import solidPlugin from "vite-plugin-solid"
export default defineConfig({
plugins: [solidPlugin()],
})入口文件配置如下:
import { render } from "solid-js/web"
import App from "./App"
render(() => <App />, document.getElementById("root"))接下来就可以开始写业务代码了,就是这么简单~
import { Title, List, Chart } from "./components"
import { onMount, onCleanup, createSignal } from "solid-js"
import request from "./utils/request"
import type { Component } from "solid-js"
import type { DataProps, ValueType } from "./typings"
import cls from "./index.module.less"
const URL = "/statistic/hrm"
const App: Component = () => {
const [getValue, setValue] = createSignal<ValueType>(null)
// mount
onMount(() => {
request<DataProps>({ method: "GET", url: URL }).then((res) => {
if (res) {
console.log("res", res)
setValue(res)
}
})
})
// unmount
onCleanup(() => {
// ...
})
return (
<div class={cls.App}>
<Title />
<List list={getValue()?.list} />
{getValue()?.pieData && <Chart data={getValue()?.pieData} />}
</div>
)
}
export default AppList 组件
import type { Component } from "solid-js"
import { ListProps } from "../typings"
import cls from "../index.module.less"
// List
const List: Component<{ list: ListProps[] }> = (props) => {
return (
<ul class={cls.list}>
{props.list?.map(({ label, value }) => (
<li>
<div class={cls.label}>{label}</div>
<div class={cls.value}>{value}</div>
</li>
))}
</ul>
)
}
export default ListEchart 可视化组件
import { onMount, onCleanup } from "solid-js"
import type { Component } from "solid-js"
import echarts, { ECOptionPie } from "../../utils/echart"
import { OptionProps } from "../../typings"
// Chart
const Chart: Component<{ data: OptionProps[] }> = (props) => {
let container: null | HTMLDivElement = null
let instance
// 性别分布
const Option: ECOptionPie = {
// data: props.data || [],
// ...
}
onMount(() => {
instance = echarts.init(container)
instance.setOption(Option)
window.addEventListener("resize", () => instance?.resize())
})
onCleanup(() => {
window.removeEventListener("resize", () => instance?.resize())
})
return <div ref={container}></div>
}
export default Chart以上是我基于项目简化的 demo,怎么样,看起来是不是和 react 特别像🤓,使用起来也是相当简单了~
总结
自 react 和虚拟 DOM 诞生以来,整个前端的开发范式都发生了翻天覆地的变化,各种类似框架也是层出不穷,他们各有各的优势。
对我们开发者来说,对于同一类型框架熟练掌握一种足矣,大可不必每种框架都学习一遍。我们需要做到对其内部实现原理的知悉,做到知其然也知其所以然,正所谓一法通万法皆通。当我们打牢基础之后再去使用和学习其他框架便轻而易举了,并在实践中拓展知识广度和深度。
对于不同类型框架,了解其优势以及一些独有的特殊思路和实现,做到心中有数,也有益于我们的技术成长。
这样我们在之后的实际开发过程中便可结合具体场景做到更合适的技术选型~
到此这篇关于基于 JavaScript 介绍性能爆表的 SolidJS 的文章就介绍到这了,更多相关 JS SolidJS 内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

