基于JavaScript介紹性能爆表的SolidJS
前言
使用預(yù)編譯、無虛擬DOM、究極融合怪、性能爆表、React的異父異母親兄弟——SolidJs
![]() | ![]() |
背景
前段時間,產(chǎn)品提了個臨時需求,讓我給開發(fā)一個獨立部署的首頁可視化頁面,因為目前我們系統(tǒng)提供以iframe的方式實現(xiàn)用戶自己選擇URL配置一個頁面,作為應(yīng)用的其中一個路由頁面使用~
想著就單獨部署一個頁面,也沒必要使用React或者Vue了,簡單畫個頁面完事,jq我是用不動了
想著最近出社區(qū)也涌現(xiàn)了不少有趣的框架,之前看svelte,就覺得挺有意思的,感覺也比較符合我的使用場景,正準(zhǔn)備用這個上手搞一波呢
然后,在GitHub發(fā)現(xiàn)了solidjs這個項目,大致看了下,好家伙,這簡直比react還react??~
看了下文檔,寫了demo試了下,很容易上手,又看了一些對比測試和博客介紹,感覺性能很強啊,和svelte一樣都是預(yù)編譯,沒有運行時,構(gòu)建產(chǎn)物十幾kb,與原生js相差無幾,令人驚嘆~
對于我這種小項目還是比較適合的~
介紹
官方介紹:用于構(gòu)建用戶界面的聲明式、高效且靈活的 JavaScript
Solid 使用了類似 Svelte 的預(yù)編譯,語法使用上類似于 React,使用 JSX 語法和非常相像的API,但不同于 React,組件只會初始化一次,并不是 state 改變就重新運行渲染整個組件,這類似于 Vue3 的 setup和響應(yīng)式更新(更新顆粒度為節(jié)點級)
官方給出的理由:
- 高性能 - 始終在公認的
UI速度和內(nèi)存利用率基準(zhǔn)測試中名列前茅 - 強大 - 可組合的反應(yīng)式原語與
JSX的靈活性相結(jié)合 - 務(wù)實 - 合理且量身定制的
API使開發(fā)變得有趣而簡單 - 生產(chǎn)力 - 人體工程學(xué)和熟悉程度使構(gòu)建簡單或復(fù)雜的東西變得輕而易舉
主要優(yōu)勢
- 高性能 - 接近原生的性能,在 js-framework-benchmark 排名中名列前茅
- 極小的打包體積 - 編譯為直接的
DOM操作,無虛擬DOM,極小的運行時(類似于Svelte),適合打為獨立的webComponent在其它應(yīng)用中嵌入 - 易于使用 - 近似
React的使用體驗,便于快速上手
對比分析
我們把關(guān)注點聚焦于是否使用虛擬DOM,以及數(shù)據(jù)的響應(yīng)處理。
虛擬DOM的分析
首先,虛擬DOM并不是一定比原生性能好,或者說是更快,拋開真實場景不談都是瞎扯淡,框架的設(shè)計和應(yīng)用場景是有它自身考量的。
在狀態(tài)與Dom操作之間抽象出一層虛擬Dom,需要犧牲一定的運行時性能,并不一定比直接操作原生Dom快,要看情況,畢竟diff并不是免費的。
- 不管你的數(shù)據(jù)變化多少,每次重繪的性能都是可以接受(提供過的去的性能)。
- 你依然可以用類似
innerHTML的思路去寫你的應(yīng)用。 - 最最重要的一點,實現(xiàn)了跨平臺。
如
react,對于web端的渲染可以使用react-dom,對于native的渲染可以使用react-native、以及服務(wù)端渲染等,他們的開發(fā)模式非常類似,按照react的語法規(guī)則進行即可,但是在render層,只要符合react api規(guī)范,你可以提供各種不同的render渲染函數(shù),進行跨平臺的渲染實現(xiàn)。
核心原理的選擇
拿我們熟悉的react和vue說明:
- React對數(shù)據(jù)的處理是不可變(
immutable):具體表現(xiàn)是整樹更新,更新時,不關(guān)注是具體哪個狀態(tài)變化了,只要有狀態(tài)改變,直接整樹diff找出差異進行對應(yīng)更新。 - Vue對數(shù)據(jù)的處理是響應(yīng)式、可變的(
mutable):更新時,能夠精確知道是哪些狀態(tài)發(fā)生了改變,能夠?qū)崿F(xiàn)精確到節(jié)點級別的更新(類似的框架還有Svelte、SolidJS)。
更新粒度的選擇
- 應(yīng)用級:有狀態(tài)改變,就更新整個應(yīng)用,生成新的虛擬Dom樹,與舊樹進行
Diff(代表作:React,當(dāng)然了,現(xiàn)在它的虛擬Dom已升級為了Fiber)。 - 組件級:與上方類似,只不過粒度小了一個等級(代表作:
vuev2及之后的版本)。 - 節(jié)點級:狀態(tài)更新直接與具體的更新節(jié)點的操作綁定(代表作
vue1.x、Svelte、SolidJS)。
vue1.x時代,對于數(shù)據(jù)是每個生成一個對應(yīng)的Wather,更新顆粒度為節(jié)點級別,但這樣創(chuàng)建大量的Wather會造成極大的性能開銷,因此在vue2.x時代,通過引入虛擬DOM優(yōu)化響應(yīng),做到了組件級顆粒度的更新。而對于
react來說,虛擬DOM就是至關(guān)重要的部分,甚至是核心,我們已經(jīng)了解react是屬于應(yīng)用級別的更新,因此整個DOM樹的更新開銷是極大的,所以這里對于虛擬DOM+diff算法的使用就是極其必要的。包括現(xiàn)在的fiber架構(gòu)與可中斷更新,也算是對虛擬DOM的極致壓榨。
是否采用虛擬DOM
這個選擇是與上邊采用何種粒度的更新設(shè)計緊密相關(guān)的:
- 是:對應(yīng)用級的這種更新粒度,虛擬Dom簡直是必需品,因為在
diff前它并不能得到此次更新的具體節(jié)點信息,必須要通過隨后的虛擬Dom+Diff算法篩選出最小差異,不然整樹append對性能是災(zāi)難(代表框架:React、vue)。- 但這里值得注意的事是:本質(zhì)上
vue并不需要虛擬DOM,因為它這種基于依賴收集的響應(yīng)式機制可以直接進行節(jié)點級更新,但vue借助虛擬DOM的抽象能力,可以做到更新粒度的隨意調(diào)整(目前是組件級),給vue的發(fā)展提供更多可能性, 尤其在跨平臺渲染方面,這點十分關(guān)鍵。
- 但這里值得注意的事是:本質(zhì)上
- 否:對節(jié)點級更新粒度的框架來說,一般沒有必要采用虛擬dom(代表作:
vue1.x、Svelte、SolidJS)。
開發(fā)語法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這種后發(fā)優(yōu)勢,沒有React沉重的歷史包袱,比如不需要處理類組件的兼容(SolidJS只支持函數(shù)式)這讓它在實現(xiàn)了大部分React功能特性的前提下,源碼體積要比React小很多,這讓它在首屏加載方面就首先占據(jù)上風(fēng)。直接調(diào)用編譯好的DOM操作方法,省去了虛擬DOM比較這一步所消耗的時間,整個更新鏈路相比React變得簡潔許多。
調(diào)用棧分析:


簡單分析:
- 組件函數(shù)只會在整個應(yīng)用生命周期里調(diào)用一次。
- 心智模型與react完全不一樣,反而與vue3保持了一致,可以說兼具了
React hooks+vue3的優(yōu)點 createEffect自動追蹤依賴,不需要像react那樣維護一個dep數(shù)組hook調(diào)用順序沒要求,以函數(shù)調(diào)用的方式解決Proxy目標(biāo)必須是對象的問題
它的響應(yīng)式實現(xiàn)確實是與vue一樣,都是基于發(fā)布訂閱的依賴收集去做的,但它沒有采用vue虛擬Dom的運行時diff,而是充分在編譯階段做文章,將狀態(tài)更新編譯為獨立的DOM操作方法。
編譯內(nèi)容分析
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函數(shù)操作。
再結(jié)合以后的webcomponent考慮下,真是大有可為,發(fā)展空間很大,未來可期~
項目實戰(zhàn)
直接按照官方文檔示例,創(chuàng)建一個支持TypeScript的基礎(chǔ)項目,模板默認使用vite構(gòu)建(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"))接下來就可以開始寫業(yè)務(wù)代碼了,就是這么簡單~
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特別像??,使用起來也是相當(dāng)簡單了~
總結(jié)
自react和虛擬DOM誕生以來,整個前端的開發(fā)范式都發(fā)生了翻天覆地的變化,各種類似框架也是層出不窮,他們各有各的優(yōu)勢。
對我們開發(fā)者來說,對于同一類型框架熟練掌握一種足矣,大可不必每種框架都學(xué)習(xí)一遍,我們需要做到對其內(nèi)部實現(xiàn)原理的知悉,做到知其然也知其所以然,正所謂一法通萬法皆通,當(dāng)我們打牢基礎(chǔ)之后再去使用和學(xué)習(xí)其他框架便輕而易舉了,并在實踐中拓展知識廣度和深度。
對于不同類型框架,了解其優(yōu)勢以及一些獨有的特殊思路和實現(xiàn),做到心中有數(shù),也有益于我們的技術(shù)成長。
這樣我們在之后的實際開發(fā)過程中便可結(jié)合具體場景做到更合適的技術(shù)選型~
到此這篇關(guān)于基于JavaScript介紹性能爆表的SolidJS的文章就介紹到這了,更多相關(guān)JS SolidJS內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript閉包_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了JavaScript閉包,閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)2017-06-06
手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash
這篇文章主要介紹了手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash,接下來將會帶你們了解下這兩者的區(qū)別,以及我們該如何手寫實現(xiàn)這兩個函數(shù)2022-03-03
跟我學(xué)習(xí)javascript的執(zhí)行上下文
跟我學(xué)習(xí)javascript的執(zhí)行上下文,讀完本文后,你應(yīng)該清楚了解釋器做了什么,為什么函數(shù)和變量能在聲明前使用以及它們的值是如何決定的,需要了解這些內(nèi)容的朋友可以參考下2015-11-11



