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


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



