基于JavaScript介紹性能爆表的SolidJS
前言
使用預(yù)編譯、無虛擬DOM、究極融合怪、性能爆表、React的異父異母親兄弟——SolidJs
![]() | ![]() |
背景
前段時間,產(chǎn)品提了個臨時需求,讓我給開發(fā)一個獨(dú)立部署的首頁可視化頁面,因?yàn)槟壳拔覀兿到y(tǒng)提供以iframe
的方式實(shí)現(xiàn)用戶自己選擇URL
配置一個頁面,作為應(yīng)用的其中一個路由頁面使用~
想著就單獨(dú)部署一個頁面,也沒必要使用React
或者Vue
了,簡單畫個頁面完事,jq
我是用不動了
想著最近出社區(qū)也涌現(xiàn)了不少有趣的框架,之前看svelte
,就覺得挺有意思的,感覺也比較符合我的使用場景,正準(zhǔn)備用這個上手搞一波呢
然后,在GitHub發(fā)現(xiàn)了solidjs
這個項(xiàng)目,大致看了下,好家伙,這簡直比react
還react
??~
看了下文檔,寫了demo試了下,很容易上手,又看了一些對比測試和博客介紹,感覺性能很強(qiáng)啊,和svelte
一樣都是預(yù)編譯,沒有運(yùn)行時,構(gòu)建產(chǎn)物十幾kb,與原生js相差無幾,令人驚嘆~
對于我這種小項(xiàng)目還是比較適合的~
介紹
官方介紹:用于構(gòu)建用戶界面的聲明式、高效且靈活的 JavaScript
Solid
使用了類似 Svelte
的預(yù)編譯,語法使用上類似于 React
,使用 JSX
語法和非常相像的API,但不同于 React
,組件只會初始化一次,并不是 state
改變就重新運(yùn)行渲染整個組件,這類似于 Vue3
的 setup
和響應(yīng)式更新(更新顆粒度為節(jié)點(diǎn)級)
官方給出的理由:
- 高性能 - 始終在公認(rèn)的
UI
速度和內(nèi)存利用率基準(zhǔn)測試中名列前茅 - 強(qiáng)大 - 可組合的反應(yīng)式原語與
JSX
的靈活性相結(jié)合 - 務(wù)實(shí) - 合理且量身定制的
API
使開發(fā)變得有趣而簡單 - 生產(chǎn)力 - 人體工程學(xué)和熟悉程度使構(gòu)建簡單或復(fù)雜的東西變得輕而易舉
主要優(yōu)勢
- 高性能 - 接近原生的性能,在 js-framework-benchmark 排名中名列前茅
- 極小的打包體積 - 編譯為直接的
DOM操作
,無虛擬DOM
,極小的運(yùn)行時(類似于Svelte
),適合打?yàn)楠?dú)立的webComponent
在其它應(yīng)用中嵌入 - 易于使用 - 近似
React
的使用體驗(yàn),便于快速上手
對比分析
我們把關(guān)注點(diǎn)聚焦于是否使用虛擬DOM,以及數(shù)據(jù)的響應(yīng)處理。
虛擬DOM的分析
首先,虛擬DOM并不是一定比原生性能好,或者說是更快,拋開真實(shí)場景不談都是瞎扯淡,框架的設(shè)計(jì)和應(yīng)用場景是有它自身考量的。
在狀態(tài)與Dom操作之間抽象出一層虛擬Dom,需要犧牲一定的運(yùn)行時性能,并不一定比直接操作原生Dom快,要看情況,畢竟diff
并不是免費(fèi)的。
- 不管你的數(shù)據(jù)變化多少,每次重繪的性能都是可以接受(提供過的去的性能)。
- 你依然可以用類似
innerHTML
的思路去寫你的應(yīng)用。 - 最最重要的一點(diǎn),實(shí)現(xiàn)了跨平臺。
如
react
,對于web端的渲染可以使用react-dom
,對于native的渲染可以使用react-native
、以及服務(wù)端渲染等,他們的開發(fā)模式非常類似,按照react
的語法規(guī)則進(jìn)行即可,但是在render
層,只要符合react api
規(guī)范,你可以提供各種不同的render
渲染函數(shù),進(jìn)行跨平臺的渲染實(shí)現(xiàn)。
核心原理的選擇
拿我們熟悉的react
和vue
說明:
- React對數(shù)據(jù)的處理是不可變(
immutable
):具體表現(xiàn)是整樹更新,更新時,不關(guān)注是具體哪個狀態(tài)變化了,只要有狀態(tài)改變,直接整樹diff找出差異進(jìn)行對應(yīng)更新。 - Vue對數(shù)據(jù)的處理是響應(yīng)式、可變的(
mutable
):更新時,能夠精確知道是哪些狀態(tài)發(fā)生了改變,能夠?qū)崿F(xiàn)精確到節(jié)點(diǎn)級別的更新(類似的框架還有Svelte、SolidJS)。
更新粒度的選擇
- 應(yīng)用級:有狀態(tài)改變,就更新整個應(yīng)用,生成新的虛擬Dom樹,與舊樹進(jìn)行
Diff
(代表作:React,當(dāng)然了,現(xiàn)在它的虛擬Dom已升級為了Fiber
)。 - 組件級:與上方類似,只不過粒度小了一個等級(代表作:
vuev2
及之后的版本)。 - 節(jié)點(diǎn)級:狀態(tài)更新直接與具體的更新節(jié)點(diǎn)的操作綁定(代表作
vue1.x
、Svelte
、SolidJS
)。
vue1.x
時代,對于數(shù)據(jù)是每個生成一個對應(yīng)的Wather
,更新顆粒度為節(jié)點(diǎn)級別,但這樣創(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è)計(jì)緊密相關(guān)的:
- 是:對應(yīng)用級的這種更新粒度,虛擬Dom簡直是必需品,因?yàn)樵?code>diff前它并不能得到此次更新的具體節(jié)點(diǎn)信息,必須要通過隨后的
虛擬Dom+Diff算法
篩選出最小差異,不然整樹append
對性能是災(zāi)難(代表框架:React
、vue
)。- 但這里值得注意的事是:本質(zhì)上
vue
并不需要虛擬DOM
,因?yàn)樗@種基于依賴收集的響應(yīng)式機(jī)制可以直接進(jìn)行節(jié)點(diǎn)級更新,但vue
借助虛擬DOM
的抽象能力,可以做到更新粒度的隨意調(diào)整(目前是組件級),給vue的發(fā)展提供更多可能性, 尤其在跨平臺渲染方面,這點(diǎn)十分關(guān)鍵。
- 但這里值得注意的事是:本質(zhì)上
- 否:對節(jié)點(diǎn)級更新粒度的框架來說,一般沒有必要采用虛擬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
的雙胞胎兄弟...
而且因?yàn)?strong>SolidJS這種后發(fā)優(yōu)勢,沒有React沉重的歷史包袱,比如不需要處理類組件的兼容(SolidJS只支持函數(shù)式)這讓它在實(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)點(diǎn) createEffect
自動追蹤依賴,不需要像react那樣維護(hù)一個dep
數(shù)組hook
調(diào)用順序沒要求,以函數(shù)調(diào)用的方式解決Proxy
目標(biāo)必須是對象的問題
它的響應(yīng)式實(shí)現(xiàn)確實(shí)是與vue一樣,都是基于發(fā)布訂閱的依賴收集去做的,但它沒有采用vue虛擬Dom的運(yùn)行時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)行時代碼。而是使用了solid-js/web
庫提供的insert
等DOM函數(shù)操作。
再結(jié)合以后的webcomponent
考慮下,真是大有可為,發(fā)展空間很大,未來可期~
項(xiàng)目實(shí)戰(zhàn)
直接按照官方文檔示例,創(chuàng)建一個支持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"))
接下來就可以開始寫業(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 App
List組件
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 List
Echart可視化組件
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)目簡化的demo,怎么樣,看起來是不是和react特別像??,使用起來也是相當(dāng)簡單了~
總結(jié)
自react和虛擬DOM誕生以來,整個前端的開發(fā)范式都發(fā)生了翻天覆地的變化,各種類似框架也是層出不窮,他們各有各的優(yōu)勢。
對我們開發(fā)者來說,對于同一類型框架熟練掌握一種足矣,大可不必每種框架都學(xué)習(xí)一遍,我們需要做到對其內(nèi)部實(shí)現(xiàn)原理的知悉,做到知其然也知其所以然,正所謂一法通萬法皆通,當(dāng)我們打牢基礎(chǔ)之后再去使用和學(xué)習(xí)其他框架便輕而易舉了,并在實(shí)踐中拓展知識廣度和深度。
對于不同類型框架,了解其優(yōu)勢以及一些獨(dú)有的特殊思路和實(shí)現(xiàn),做到心中有數(shù),也有益于我們的技術(shù)成長。
這樣我們在之后的實(shí)際開發(fā)過程中便可結(jié)合具體場景做到更合適的技術(shù)選型~
到此這篇關(guān)于基于JavaScript介紹性能爆表的SolidJS的文章就介紹到這了,更多相關(guān)JS SolidJS內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
鼠標(biāo)移動到圖片名上,顯示圖片的簡單實(shí)例
鼠標(biāo)移動到名(wait.gif)上,顯示圖片,鼠標(biāo)移開則不顯示圖片2013-07-07JavaScript閉包_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了JavaScript閉包,閉包(closure)是Javascript語言的一個難點(diǎn),也是它的特色,很多高級應(yīng)用都要依靠閉包實(shí)現(xiàn)2017-06-06手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash
這篇文章主要介紹了手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash,接下來將會帶你們了解下這兩者的區(qū)別,以及我們該如何手寫實(shí)現(xiàn)這兩個函數(shù)2022-03-03跟我學(xué)習(xí)javascript的執(zhí)行上下文
跟我學(xué)習(xí)javascript的執(zhí)行上下文,讀完本文后,你應(yīng)該清楚了解釋器做了什么,為什么函數(shù)和變量能在聲明前使用以及它們的值是如何決定的,需要了解這些內(nèi)容的朋友可以參考下2015-11-11