能大家在看到這個標(biāo)題的時候,會覺得,只不過又是一篇爛大街的 SSR 從零入門的教程而已。別急,往下看,相信你或多或少會有一些不一樣的收獲呢。
在落地一種技術(shù)的時候,我們首先要想一想:
上面三個問題思考清楚之后,才能真正地去落地。上面三個問題思考清楚之后,才能真正地去落地。而有贊教育接入服務(wù)端渲染,正是為了優(yōu)化 H5 頁面的首屏內(nèi)容到達(dá)時間,帶來更好的用戶體驗(順便利于 SEO)。
說了這么多,以下開始正文。
一、后端模版引擎時代
在較早時期,前后端的配合模式為:后端負(fù)責(zé)服務(wù)層、業(yè)務(wù)邏輯層和模版渲染層(表現(xiàn)層);前端只是實現(xiàn)頁面的交互邏輯以及發(fā)送 AJAX。比較典型的例子就是 JSP 或 FreeMarker 模板引擎負(fù)責(zé)渲染出 html 字符串模版,字符串模版里的 js 靜態(tài)資源才是真正前端負(fù)責(zé)的東西。
而這種形式,就是天然的服務(wù)端渲染模式:用戶請求頁面 -> 請求發(fā)送到應(yīng)用服務(wù)器 -> 后端根據(jù)用戶和請求信息獲取底層服務(wù) -> 根據(jù)服務(wù)返回的數(shù)據(jù)進(jìn)行組裝,同時 JSP 或 FreeMarker 模版引擎根據(jù)組裝的數(shù)據(jù)渲染為 html 字符串 -> 應(yīng)用服務(wù)器講 html 字符串返回給瀏覽器 -> 瀏覽器解析 html 字符串渲染 UI 及加載靜態(tài)資源 -> js 靜態(tài)資源加載完畢界面可交互。
那么既然后端模版引擎時代帶來的效果就是我們想要的,那為啥還有以后讓前端發(fā)展服務(wù)端渲染呢?因為很明顯,這種模式從開發(fā)角度來講還有挺多的問題,比如:
二、SPA 時代
后來,誕生了 SPA(Single Page Application),解決了上面說的部分問題:
但同時,也帶來了一些問題:
三、服務(wù)端渲染
正因為 SPA 帶來的一些問題(尤其是首屏白屏的問題),接入服務(wù)端渲染顯得尤為必要。// 終于講到服務(wù)端渲染這個重點了。
而正是 Node 的發(fā)展和基于 Virtual DOM 的前端框架的出現(xiàn),使得用 js 實現(xiàn)服務(wù)端渲染成為可能。因此在 SPA 的優(yōu)勢基礎(chǔ)上,我們順便解決了因為 SPA 引入的問題:
3.1 實現(xiàn)
既然服務(wù)端渲染能帶來這么多好處,那具體怎么實現(xiàn)呢?從官網(wǎng)給出的原理圖,我們可以清晰地看出:
3.2 優(yōu)化
按照 Vue SSR 官方文檔建立起一個服務(wù)端渲染的工程后,是否就可以直接上線了呢?別急,我們先看看是否有什么可以優(yōu)化的地方。
3.2.1 路由和代碼分割
一個大的 SPA,主文件 js 往往很大,通過代碼分割可以將主文件 js 拆分為一個個單獨的路由組件 js 文件,可以很大程度上減小首屏的資源加載體積,其他路由組件可以預(yù)加載。
復(fù)制代碼
// router.js constIndex=()=>import(/* webpackChunkName: "index" */'./pages/Index.vue'); constDetail=()=>import(/* webpackChunkName: "detail" */'./pages/Detail.vue'); constroutes=[ { path:'/', component: Index }, { path:'/detail', component: Detail } ]; constrouter=newRouter({ mode:'history', routes });
3.2.2 部分模塊(不需要 SSR 的模塊)客戶端渲染
因為服務(wù)端渲染是 CPU 密集型操作,非首屏的模塊或者不重要的模塊(比如底部的推薦列表)完全可以采用客戶端渲染,只有首屏的核心模塊采用服務(wù)端渲染。這樣做的好處是明顯的:1. 較大地節(jié)省 CPU 資源;2. 減小了服務(wù)端渲染直出的 html 字符串長度,能夠更快地響應(yīng)給瀏覽器,減小白屏?xí)r間。
復(fù)制代碼
// Index.vue asyncData({ store }) { returnthis.methods.dispatch(store);// 核心模塊數(shù)據(jù)預(yù)取,服務(wù)端渲染 } mounted() { this.initOtherModules();// 非核心模塊,客戶端渲染,在 mounted 生命周期鉤子里觸發(fā) }
3.2 3 頁面緩存 / 組件緩存
頁面緩存一般適用于狀態(tài)無關(guān)的靜態(tài)頁面,命中緩存直接返回頁面;組件緩存一般適用于純靜態(tài)組件,也可以一定程度上提升性能。
復(fù)制代碼
// page-level caching constmicroCache=LRU({ max:100, maxAge:1000// 重要提示:條目在 1 秒后過期。 }) server.get('*', (req, res)=> { consthit=microCache.get(req.url) if(hit) {// 命中緩存,直接返回頁面 returnres.end(hit) } // 服務(wù)端渲染邏輯 ... })
復(fù)制代碼
// component-level caching // server.js constLRU=require('lru-cache') constrenderer=createRenderer({ cache: LRU({ max:10000, maxAge: ... }) }); // component.js exportdefault{ name:'item',// 必填選項 props: ['item'], serverCacheKey:props=>props.item.id, render (h) { returnh('div',this.item.id) } };
3.2.4 頁面靜態(tài)化
如果工程中大部分頁面都是狀態(tài)相關(guān)的,所以技術(shù)選型采用了服務(wù)端渲染,但有部分頁面是狀態(tài)無關(guān)的,這個時候用服務(wù)端渲染就有點浪費(fèi)資源了。像這些狀態(tài)無關(guān)的頁面,完全可以通過 Nginx Proxy Cache 緩存到 Nginx 服務(wù)器,可以避免這些流量打到應(yīng)用服務(wù)器集群,同時也能減少響應(yīng)的時間。
3.3 降級
進(jìn)行優(yōu)化之后,是否就可以上線了呢?這時我們想一想,萬一服務(wù)端渲染出錯了怎么辦?萬一服務(wù)器壓力飆升了怎么辦(因為服務(wù)端渲染是 CPU 密集型操作,很耗 CPU 資源)?為了保證系統(tǒng)的高可用,我們需要設(shè)計一些降級方案來避免這些。具體可以采用的降級方案有:
3.4 上線前準(zhǔn)備
3.4.1 壓測
壓測可以分為多個階段:本地開發(fā)階段、QA 性能測試階段、線上階段。
3.4.2 日志
作為生產(chǎn)環(huán)境的應(yīng)用,肯定不能“裸奔”,必須接入日志平臺,將一些報錯信息收集起來,以便之后問題的排查。
3.4.3 灰度
如果上線服務(wù)端渲染的工程是提供核心服務(wù)的應(yīng)用,應(yīng)該采用灰度發(fā)布的方式,避免全量上線。一般灰度方案可以采用:百分比灰度、白名單灰度、自定義標(biāo)簽灰度。具體采用哪種灰度方式看場景自由選擇,每隔一段時間觀察灰度集群沒有問題,所以漸漸增大灰度比例 / 覆蓋范圍,直到全量發(fā)布。
3.5 落地
在有贊電商的服務(wù)端渲染的落地場景中,我們抽離了單獨的依賴包,提供各個能力。
3.6 效果
從最終的上線效果來看,相同功能的頁面,服務(wù)端渲染的首屏內(nèi)容時間比客戶端渲染提升了 300%+。
3.7 Q & A
Q1:為什么服務(wù)端渲染就比客戶端渲染快呢?
A:首先我們明確一點,服務(wù)端渲染比客戶端渲染快的是首屏的內(nèi)容到達(dá)時間(而非首屏可交互時間)。至于為什么會更快,我們可以從兩者的 DOM 渲染過程來對比:
客戶端渲染:瀏覽器發(fā)送請求 -> CDN / 應(yīng)用服務(wù)器返回空 html 文件 -> 瀏覽器接收到空 html 文件,加載的 css 和 js 資源 -> 瀏覽器發(fā)送 css 和 js 資源請求 -> CDN / 應(yīng)用服務(wù)器返回 css 和 js 文件 -> 瀏覽器解析 css 和 js -> js 中發(fā)送 ajax 請求到 Node 應(yīng)用服務(wù)器 -> Node 服務(wù)器調(diào)用底層服務(wù)后返回結(jié)果 -> 前端拿到結(jié)果 setData 觸發(fā) vue 組件渲染 -> 組件渲染完成
服務(wù)端渲染:瀏覽器發(fā)送請求 -> Node 應(yīng)用服務(wù)器匹配路由 -> 數(shù)據(jù)預(yù)取:Node 服務(wù)器調(diào)用底層服務(wù)拿到 asyncData 存入 store -> Node 端根據(jù) store 生成 html 字符串返回給瀏覽器 -> 瀏覽器接收到 html 字符串將其激活:
我們可以很明顯地看出,客戶端渲染的組件渲染強(qiáng)依賴 js 靜態(tài)資源的加載以及 ajax 接口的返回時間,而通常一個 page.js 可能會達(dá)到幾十 KB 甚至更多,很大程度上制約了 DOM 生成的時間。而服務(wù)端渲染從用戶發(fā)出一次頁面 url 請求之后,應(yīng)用服務(wù)器返回的 html 字符串就是完備的計算好的,可以交給瀏覽器直接渲染,使得 DOM 的渲染不再受靜態(tài)資源和 ajax 的限制。
Q2:服務(wù)端渲染有哪些限制?
A:比較常見的限制比如:
Q3:如果我的需求只是生成文案類的靜態(tài)頁面,需要用到服務(wù)端渲染嗎?
A:像這些和用戶狀態(tài)無關(guān)的靜態(tài)頁面,完全可以采用預(yù)渲染的方式(具體見 Vue SSR 官方指南),服務(wù)端渲染適用的更多場景會是狀態(tài)相關(guān)的(比如用戶信息相關(guān)),需要經(jīng)過 CPU 計算才能輸出完備的 html 字符串,因此服務(wù)端渲染是一個 CPU 密集型的操作。而靜態(tài)頁面完全不需要涉及任何復(fù)雜計算,通過預(yù)渲染更快且更節(jié)省 CPU 資源。
、為什么xml
需要服務(wù)器端返回少量的、單一的數(shù)據(jù)
用戶名是否可用 1 / 0
返回兩個數(shù)的和 400
登錄是否成功 true/false
數(shù)據(jù)插是否成功 true/false
如果我們需要從服務(wù)器端返回大量、復(fù)雜的數(shù)據(jù),如何實現(xiàn)?
xml:服務(wù)器端返回xml數(shù)據(jù)
json:服務(wù)器端返回json數(shù)據(jù)
2、格式:
(1)php解析xml
$dom=new DOMDocument();
$dom->loadXML($str);
$nd=$dom->getElementsByTagName("TagName");
$value=$nd->item(0)->nodeValue
$xml=simplexml_load_string($str);
$first=$xml->first;
$second=$xml->second;
(2)js解析xml
var xml=xmlHttp.responseXML;
node=xml.getElementsByTagName("TagName");
node[0].childNodes[0].nodeValue;
3 、案例1:
實現(xiàn)兩個數(shù)的四則運(yùn)算
HTML代碼:
<script language="javascript" src="public.js"></script>
<script>
window.onload=function(){
$('btnOk').onclick=function(){
var f=$('first').value;
var s=$('second').value;
var data='first='+f+'&second='+s;
var xhr=createxhr();
xhr.open('post','demo01.php');
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
//xml --->xml dom對象
var xml=xhr.responseXML;
var str=xml.getElementsByTagName('jia')[0].childNodes[0].nodeValue;
str+='|'+xml.getElementsByTagName('jian')[0].childNodes[0].nodeValue;
str+='|'+xml.getElementsByTagName('cheng')[0].childNodes[0].nodeValue;
str+='|'+xml.getElementsByTagName('chu')[0].childNodes[0].nodeValue;
$('result').innerHTML=str;
}
};
xhr.send(data);
};
};
</script>
<input type="text" id="first" /><br>
<input type="text" id='second' /><br>
<div id='result'></div>
<input type="button" id="btnOk" value="計算" />
理解:
var xml=xhr.responseXML; 得到ajax返回的xmldom對象
xml.getElementsByTagName('jia')[0] :是表示獲取jia這個元素
xml.getElementsByTagName('jia')[0].childNodes:表示獲取jia元素下的所有子節(jié)點
xml.getElementsByTagName('jia')[0].childNodes[0] :表示獲取jia元素下的唯一文本節(jié)點
xml.getElementsByTagName('jia')[0].childNodes[0].nodeValue:文本節(jié)點的值
php代碼:
<?php
$first=$_POST['first'];
$second=$_POST['second'];
$result1=$first+$second;
$result2=$first-$second;
$result3=$first*$second;
$result4=$first/$second;
//要想返回xml,首先連接一個xml格式的字符串
$str='<root>';
$str.='<jia>'.$result1.'</jia>';
$str.='<jian>'.$result2.'</jian>';
$str.='<cheng>'.$result3.'</cheng>';
$str.='<chu>'.$result4.'</chu>';
$str.='</root>';
/*$str=<<<str
<root>
<jia>$result1</jia>
</root>
str;*/
header('Content-type:text/xml');
echo $str;
理解:
得到結(jié)果后,需要使用字符串連接成一個xml格式的字符串,如:需要一個根元素,下面子元素,最后是具體的值,
連接時也可以使用<<<str創(chuàng)建xml字符串
str;
輸出這個字符串時,默認(rèn)的響應(yīng)內(nèi)容類型:text/html,也就是說客戶端仍把代碼當(dāng)做html來進(jìn)行解析,
ajax對象的responeXML是不能得到一個xmldom對象,必須設(shè)置響應(yīng)頭類型為:text/xml,其代碼:header('Content-type:text/xml');
public.js:
function createxhr() {
/*var xhr;
var str=window.navigator.userAgent;
if (str.indexOf('MSIE') > 0) {
xhr=new ActiveXObject('Microsoft.XMLHTTP');
} else {
xhr=new XMLHttpRequest();
}
return xhr;*/
try{return new XMLHttpRequest();}catch(e){}
try{return new ActiveXObject('Microsoft.XMLHTTP'); }catch(e){}
alert('請更換瀏覽器!');
}
function $(id){
return document.getElementById(id);
}
4、案例2
在頁面加載之后,將mysql數(shù)據(jù)庫goods表中所有數(shù)據(jù)顯示在表格中
<root>
<goods>
<name>222</name>
<price>55.00</price>
</goods>
<goods>
<name>諾 E661</name>
<price>205.00</price>
</goods>
<goods>
<name>諾 E661</name>
<price>200.00</price>
</goods>
</root>
HTML代碼:
<style>
tr{
background-color:#ffffff;
height:30px;
font-size:12px;
}
</style>
<script language="javascript" src='public.js'></script>
<script>
window.onload=function(){
var xhr=createxhr();
xhr.open('post','demo02.php');
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200)
var xml=xhr.responseXML;
var goods=xml.getElementsByTagName('goods');
for(var i=0;i<goods.length;i++){
//創(chuàng)建行元素
var tr=document.createElement('tr');
//創(chuàng)建序號td元素
var tdID=document.createElement('td');
tdID.innerHTML=i+1;
//創(chuàng)建名稱td元素
var tdName=document.createElement('td');
tdName.innerHTML=goods[i].childNodes[0].childNodes[0].nodeValue;
//創(chuàng)建價格td元素
var tdPrice=document.createElement('td');
tdPrice.innerHTML=goods[i].childNodes[1].childNodes[0].nodeValue;
//將三個td追加到tr元素
tr.appendChild(tdID);
tr.appendChild(tdName);
tr.appendChild(tdPrice);
document.getElementsByTagName('TBODY')[0].appendChild(tr);
}
};
xhr.send(null);
}
</script>
<table id='tbData' width="800" cellspacing="1" cellpadding="4" bgcolor="#336699">
<tr>
<td>序號</td>
<td>商品名稱</td>
<td>商品價格</td>
</tr>
</table>
理解:
創(chuàng)建行元素,
創(chuàng)建單元格元素
將單元格元素追加到行元素中
將行元素追加到表格元素中
php代碼:
<?php
//查詢goods表中所有數(shù)據(jù)并返回
$sql="select name,price from goods order by id desc";
mysql_connect('localhost','root','111111');
mysql_select_db('shop');
mysql_query('set names gb2312');
$result=mysql_query($sql); //發(fā)送sql語句
$num=mysql_num_rows($result); //總行數(shù)
$str='<root>';
for($i=0;$i<$num;$i++){
$row=mysql_fetch_assoc($result);
$str.='<goods>';
$str.='<name>'.iconv('gb2312','utf-8',$row['name']).'</name>';
$str.='<price>'.$row['price'].'</price>';
$str.='</goods>';
}
$str.='</root>';
header('Content-Type:text/xml');
echo $str;
?>
理解:
查詢goods表中所有數(shù)據(jù)
連接xml格式的字符串
表中有多少條數(shù)據(jù)
xml字符串就有幾對goods標(biāo)簽
其中, name字段出現(xiàn)中文,所以需要進(jìn)行轉(zhuǎn)碼 gb2312--utf-8
最后, 輸出xml字符串
譯 | 核子可樂、Tina
技術(shù)和軟件開發(fā)領(lǐng)域存在一種有趣的現(xiàn)象,就是同樣的模式迭起興衰、周而復(fù)始。
過去Web非常簡單。URL 指向服務(wù)器,服務(wù)器將數(shù)據(jù)混合成 html,然后在瀏覽器上呈現(xiàn)該響應(yīng)。圍繞這種簡單范式,誕生了各種Javascript框架,以前可能需要數(shù)月時間完成的一個應(yīng)用程序基本功能,現(xiàn)在借助這些框架創(chuàng)建相對復(fù)雜的項目卻只需要數(shù)小時,我們節(jié)省了很多時間,從而可以將更多精力花在業(yè)務(wù)邏輯和應(yīng)用程序設(shè)計上。
但隨著 Web 不斷地發(fā)展,Javascript 失控了。不知何故,我們決定向用戶拋出大量 App,并在使用時發(fā)出不斷增加的網(wǎng)絡(luò)請求;不知何故,為了生成 html,我們必須使用 JSON,發(fā)出數(shù)十個網(wǎng)絡(luò)請求,丟棄我們在這些請求中獲得的大部分?jǐn)?shù)據(jù),用一個越來越不透明的 JavaScript 框架黑匣子將 JSON 轉(zhuǎn)換為 html,然后將新的 html 修補(bǔ)到 DOM 中......
難道大家快忘記了我們可以在服務(wù)器上渲染 html 嗎?更快、更一致、更接近應(yīng)用程序的實際狀態(tài),并且不會向用戶設(shè)備發(fā)送任何不必要的數(shù)據(jù)?但是如果沒有 Javascript,我們必須在每次操作時重新加載頁面。
現(xiàn)在,有一個新的庫出現(xiàn)了,摒棄了定制化的方法,這就是 htmx。作為 Web 開發(fā)未來理念的一種實現(xiàn),它的原理很簡單:
htmx 出現(xiàn)在 2020 年,創(chuàng)建者 Carson Gross 說 htmx 來源自他于 2013 年研究的一個項目 intercooler.js。2020 年,他重寫了不依賴 jQuery 的 intercooler.js,并將其重命名為 htmx。然后他驚訝的發(fā)現(xiàn) Django 社區(qū)迅速并戲劇性地接受了它!
圖片來源:https://lp.jetbrains.com/django-developer-survey-2021-486/
Carson Gross 認(rèn)為 htmx 設(shè)法抓住了開發(fā)者對現(xiàn)有 Javascript 框架不滿的浪潮,“這些框架非常復(fù)雜,并且經(jīng)常將 Django 變成一個愚蠢的 JSON 生產(chǎn)者”,而 htmx 與開箱即用的 Django 配合得更好,因為它通過 html 與服務(wù)器交互,而 Django 非常擅長生成 html。
對于 htmx 的迅速走紅,Carson Gross 發(fā)出了一聲感嘆:這真是“十年窗下無人問,一舉成名天下知(this is another example of a decade-long overnight success)”。
可以肯定的一點是 htmx 絕對能用,單從理論上講,這個方法確實值得稱道。但軟件問題終究要歸結(jié)于實踐效果:效果好嗎,能不能給前端開發(fā)帶來改善?
在 DjangoCon 2022 上,Contexte 的 David Guillot 演示了他們在真實 SaaS 產(chǎn)品上實現(xiàn)了從 React 到 htmx 的遷移,而且效果非常好,堪稱“一切 htmx 演示之母”(視頻地址:https://www.youtube.com/watch?v=3GObi93tjZI)。
Contexte 的項目開始于 2017 年,其后端相當(dāng)復(fù)雜,前端 UI 也非常豐富,但團(tuán)隊非常小。所以他們在一開始的時候跟隨潮流選擇了 React 來“構(gòu)建 API 綁定 SPA、實現(xiàn)客戶端狀態(tài)管理、前后端狀態(tài)分離”等。但實際應(yīng)用中,因為 API 設(shè)計不當(dāng),DOM 樹太深,又需要加載很多信息,導(dǎo)致 UI“非常非常緩慢”。在敏捷開發(fā)的要求下,團(tuán)隊里唯一的 Javascript 專家對項目的復(fù)雜性表現(xiàn)得一無所措,因此他們決定試試 htmx。
于是我們決定大膽嘗試,花幾個月時間用簡單的 Django 模板和 htmx 替換掉了 SaaS 產(chǎn)品中已經(jīng)使用兩年的 React UI。這里我們分享了一些相關(guān)經(jīng)驗,公布各項具體指標(biāo),希望能幫同樣關(guān)注 htmx 的朋友們找到說服 CTO 的理由!
6. 將 Web 構(gòu)建時間縮短了 88%(由 40 秒縮短至 5 秒)
7. 首次加載交互時間縮短了 50% 至 60%(由 2 到 6 秒,縮短至 1 到 2 秒)
8. 使用 htmx 時可以配合更大的數(shù)據(jù)集,超越 React 的處理極限
9. Web 應(yīng)用程序的內(nèi)存使用量減少了 46%(由 75 MB 降低至 40 MB)
這些數(shù)字令人頗為意外,也反映出 Contexte 應(yīng)用程序高度契合超媒體的這一客觀結(jié)果:這是一款以內(nèi)容為中心的應(yīng)用程序,用于顯示大量文本和圖像。很明顯,其他 Web 應(yīng)用程序在遷移之后恐怕很難有同樣夸張的提升幅度。
但一些開發(fā)者仍然相信,大部分應(yīng)用程序在采用超媒體 /htmx 方法之后,肯定也迎來顯著的改善,至少在部分系統(tǒng)中大受裨益。
可能很多朋友沒有注意,移植本身對團(tuán)隊結(jié)構(gòu)也有直接影響。在 Contexte 使用 React 的時候,后端與前端之間存在硬性割裂,其中兩位開發(fā)者全職管理后端,一位開發(fā)者單純管理前端,另有一名開發(fā)者負(fù)責(zé)“全棧”。(這里的「全棧」,代表這位開發(fā)者能夠輕松接手前端和后端工作,因此能夠在整個「棧」上獨立開發(fā)功能。)
而在移植至 htmx 之后,整個團(tuán)隊全都成了“全棧”開發(fā)人員。于是每位團(tuán)隊成員都更高效,能夠貢獻(xiàn)出更多價值。這也讓開發(fā)變得更有樂趣,因為開發(fā)人員自己就能掌握完整功能。最后,轉(zhuǎn)向 htmx 也讓軟件優(yōu)化度上了一個臺階,現(xiàn)在開發(fā)人員可以在棧內(nèi)的任意位置進(jìn)行優(yōu)化,無需與其他開發(fā)者提前協(xié)調(diào)。
如今,單頁應(yīng)用(SPA)可謂風(fēng)靡一時:配合 React、Redux 或 Angular 等庫的 JS 或 TS 密集型前端,已經(jīng)成為創(chuàng)建 Web 應(yīng)用程序的主流方式。以一個需要轉(zhuǎn)譯成 JS 的 SPA 應(yīng)用為例:
但 htmx 風(fēng)潮已經(jīng)襲來,人們開始強(qiáng)調(diào)一種“傻瓜客戶端”方法,即由服務(wù)器生成 html 本體并發(fā)送至客戶端,意味著 UI 事件會被發(fā)送至服務(wù)器進(jìn)行處理。
用這個例子進(jìn)行前后對比,我們就會看到前者涉及的活動部件更多。從客戶端角度出發(fā),后者其實回避了定制化客戶端技術(shù),采取更簡單的方法將原本只作為數(shù)據(jù)引擎的服務(wù)器變成了視圖引擎。
后一種方法被稱為 AJAX(異步 JavaScript 與 XML)。這種簡單思路能夠讓 Web 應(yīng)用程序獲得更高的響應(yīng)性體驗,同時消除了糟糕的“回發(fā)”(postback,即網(wǎng)頁完全刷新),由此回避了極其低效的“viewstate”等.NET 技術(shù)。
htmx 在很多方面都體現(xiàn)出對 AJAX 思路的回歸,最大的區(qū)別就是它僅僅作為新的聲明性 html 屬性出現(xiàn),負(fù)責(zé)指示觸發(fā)條件是什么、要發(fā)布到哪個端點等。
另一個得到簡化的元素是物理應(yīng)用程序的結(jié)構(gòu)與構(gòu)建管道。因為不再涉及手工編寫 JS,而且整個應(yīng)用程序都基于服務(wù)器,因此不再對 JS 壓縮器、捆綁器和轉(zhuǎn)譯器做(即時)要求。就連客戶端項目也能解放出來,一切都由 Web 服務(wù)器項目負(fù)責(zé)完成,所有應(yīng)用程序代碼都在.NET 之上運(yùn)行。從這個角度來看,這與高度依賴服務(wù)器的 Blazor Server 編程模型倒是頗有異曲同工之妙。
技術(shù)和軟件開發(fā)領(lǐng)域存在一種有趣的現(xiàn)象,就是同樣的模式迭起興衰、周而復(fù)始。隨著 SPA 的興起,人們一度以為 AJAX 已經(jīng)過氣了,但其基本思路如今正卷土重來。這其中當(dāng)然會有不同的權(quán)衡,例如更高的服務(wù)器負(fù)載和網(wǎng)絡(luò)流量(畢竟現(xiàn)在我們發(fā)送的是數(shù)據(jù)視圖,而不只是數(shù)據(jù)),但能讓開發(fā)者多個選擇肯定不是壞事。
雖然不敢確定這種趨勢是否適用于包含豐富用戶體驗的高復(fù)雜度應(yīng)用程序,但毫無疑問,相當(dāng)一部分 Web 應(yīng)用程序并不需要完整的 SPA 結(jié)構(gòu)。對于這類用例,簡單的 htmx 應(yīng)用程序可能就是最好的解決方案。
參考鏈接:
https://news.ycombinator.com/item?id=33218439
https://www.reddit.com/r/django/comments/rxjlc6/htmx_gaining_popularity_rapidly/
https://mekhami.github.io/2021/03/26/htmx-the-future-of-web/
https://www.compositional-it.com/news-blog/more-on-htmx-back-to-the-future/
聲明:本文為InfoQ編譯,未經(jīng)許可禁止轉(zhuǎn)載。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。