明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

如何選擇Web前端模板引擎(推薦)

[摘要]本篇文章給大家?guī)淼膬?nèi)容是關(guān)于如何選擇Web前端模板引擎(推薦),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。模板引擎負(fù)責(zé)組裝數(shù)據(jù),以另外一種形式或外觀展現(xiàn)數(shù)據(jù)。 瀏覽器中的頁面是 Web 模板引擎最終的展現(xiàn)。無論你是否直接使用模板引擎,Web 模板一直都在,不在前端就在后端,它...
本篇文章給大家?guī)淼膬?nèi)容是關(guān)于如何選擇Web前端模板引擎(推薦),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

模板引擎負(fù)責(zé)組裝數(shù)據(jù),以另外一種形式或外觀展現(xiàn)數(shù)據(jù)。 瀏覽器中的頁面是 Web 模板引擎最終的展現(xiàn)。

無論你是否直接使用模板引擎,Web 模板一直都在,不在前端就在后端,它的出現(xiàn)甚至可以追溯到超文本標(biāo)記語言 HTML 標(biāo)準(zhǔn)正式確立之前。

服務(wù)器端的模板引擎

我所知道最早的 Web 模板引擎是 PHP,它正式誕生于 1997 年,工作在服務(wù)器端。讓我們看看 PHP 官方的 intro-whatis:

HPer 普遍贊同 PHP 本身就是最天然、原生的 PHP 模板引擎,因?yàn)樗緛砭褪。?PHP 的世界里多次出現(xiàn)過再包裝的模板引擎,著名的有 smarty。

其它服務(wù)器端語言很多都有 HTML 模板引擎,比如 JSP、mustache。

毫無疑問,這些服務(wù)器端模板引擎最終生成的結(jié)果是 HTML(XML) 字符串,處理流程邏輯使用宿主語言本身的語法實(shí)現(xiàn)。

它們的共同特征:HTML 只是個字符串, 最終結(jié)果可能還需要類似 Tidy 這樣的清潔或修正驗(yàn)證工具。

這里提出一個問題:二次封裝的 smarty 有存在的必要么?

瀏覽器端的模板引擎

我所知道最早的前端模板引擎是 jCT,它托管于 Google Code,誕生于 2008 年,宿主語言是 JavaScript,工作在瀏覽器中。

今天在 OSC 搜索 JavaScript 模板引擎你會得到 100+ 個結(jié)果,下邊列舉一些:

輕量度:tpl.js、T.js
認(rèn)知度:arttemplate、mustache.js、doT.js、handlebars.js、pug
DOM-tree-based:domTemplate、transparency、plates
VDOM-based:htmltemplate-vdom、virtual-stache、html-patcher
流行框架:Vue.js、ReactJS、riot
Real-DOM:PowJS

它們的共同特征:全都支持插值。

這里還有 templating-engines 受歡迎度的對比,甚至 best-javascript-templating-engines 投票及正反方的理由。

如何選擇

我認(rèn)為存在即合理,每個引擎、框架總有可取之處,至少在你的應(yīng)用里,在某個時代,所以本文不會評論某個引擎哪一點(diǎn)不好,那樣是不客觀的。現(xiàn)在回答前邊提到的問題:smarty 有存在的必要么?我的答案是:有。理由很簡單,看給誰用、看大背景。對于前后端沒有分離的應(yīng)用,或前端人員對后端語言不夠熟悉,或因崗位職責(zé)需要,那么前端人員掌握一種比較通用的模板語法(語言)是現(xiàn)實(shí)的,反之讓 PHPer 自己去使用 smarty 那就太浪費(fèi)技能了。

下面是通常意義上的引擎選擇建議:

前提,選擇的引擎能滿足數(shù)據(jù)渲染需求,且不和現(xiàn)有依賴沖突,如果你已經(jīng)非常熟悉某個引擎,那你已經(jīng)有答案了。

是一次性的項目需求么? 是的話直接選擇輕量的,學(xué)習(xí)復(fù)雜度最低的。 是要做組件開發(fā)么?

引擎支持預(yù)編譯結(jié)果,不必每次都實(shí)時編譯么?

要跨平臺么? 有官方提供支持的,首選類 React-JSX 的引擎或純粹的 VDOM 引擎。

選擇學(xué)習(xí)或維護(hù)復(fù)雜度最低的,眾所周知,開發(fā)者對調(diào)試的時間超過寫代碼的時間深惡痛絕。

最后才是性能對比,性能對比是一件非常細(xì)致的工作,他人的對比結(jié)果不一定符合你的場景。

我認(rèn)為應(yīng)該弱化語法風(fēng)格的對比,偏好是沒有可比性的,一些語法甚至有特殊的背景原因。

為什么最后才是性能對比?

性能的確很重要,但如果性能還沒有影響到你的應(yīng)用體驗(yàn)度,那就忽視它。很難真實(shí)地模擬應(yīng)用場景,通常只有通過真實(shí)場景來檢驗(yàn),目前的測試工具還達(dá)不到這種效果。

前述問題有些有固定答案,下面討論余下的問題:如何考慮組件開發(fā)、支持預(yù)編譯、復(fù)雜度?

組件開發(fā)

進(jìn)行組件開發(fā)已經(jīng)不再是選擇模板引擎的問題了,這是生態(tài)環(huán)境選擇的問題。如果你的應(yīng)用需要更快地完成,那么時間點(diǎn)是第一位的,就選擇流行框架,有足夠多的組件讓你使用或參考。如果你的應(yīng)用有獨(dú)立的生態(tài)環(huán)境,需要技術(shù)選型以便長期維護(hù),那繼續(xù)看下文。

預(yù)編譯

預(yù)編譯應(yīng)該具備:

編譯結(jié)果在目標(biāo)環(huán)境中不再需要編譯過程。
編譯結(jié)果可調(diào)試性,這意味著結(jié)果應(yīng)該包含原生 ECMAScript 代碼,而不是純粹的數(shù)據(jù)描述。
大家都知道 React-JSX 是支持預(yù)編譯的,官方的說法是 React Without JSX,即總是 build 過的。

一些基于字符串處理的引擎也支持預(yù)編譯。如果你需要預(yù)編譯,建議拋棄編譯結(jié)果依然是基于字符串拼接的引擎,那樣還不如不預(yù)編譯,那是 HTML5 未被廣泛支持之前的技術(shù)手段。

至少也要有類似 React-JSX 這樣的編譯結(jié)果才具有可調(diào)試性。備注:Vue.js 支持多種模板引擎,可達(dá)到同樣的效果。

原 ReactJS 代碼,其中用到了 Web Components 技術(shù):

class HelloMessage extends React.Component {
  render() {
    return <p>Hello <x-search>{this.props.name}</x-search>!</p>;
  }
}

編譯后:

class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      "p",
      null,
      "Hello ",
      React.createElement(
        "x-search",
        null,
        this.props.name
      ),
      "!"
    );
  }
}

不少 VDOM 引擎也可以編譯類似效果,比如 htmltemplate-vdom。

  <script>
        var id = 3;
        var env = {
            people: [
                {
                    id: 'id1',
                    name: 'John',
                    inner: [{ title: 'a1' }, { title: 'b1' }],
                    city: 'New York',
                    active: true
                },
                {
                    id: 'id2',
                    name: 'Mary',
                    inner: [{ title: 'a2' }, { title: 'b2' }],
                    city: 'Moscow'
                }
            ],
            githubLink: 'https://github.com/agentcooper/htmltemplate-vdom',
            itemClick: function(id) {
                env.people.forEach(function(person) {
                    person.active = String(person.id) === String(id);
                });
                loop.update(env);
            }
            // Omitted ....
        };
    </script>

復(fù)雜度

很難用唯一的標(biāo)準(zhǔn)去評判兩個引擎哪個復(fù)雜度低,這是由使用者的思維模式不同造成的。例如前邊列出的引擎在使用上以及預(yù)編譯結(jié)果上的區(qū)別,不同使用者感觸是不同的,這正是不同引擎存在的合理性、價值性。

有的使用者認(rèn)為這個應(yīng)用場景有字符串模板就滿足了需求,輕量夠用。
有的使用者認(rèn)為字符串拼接技術(shù)的模板引擎不夠強(qiáng)壯,不夠時代感。
有的使用者認(rèn)為OOP 夠理性,夠邏輯,夠抽象。
有的使用者認(rèn)為原生 HTML 才叫前端。
有的使用者認(rèn)為 VDOM 適用性更廣。

這些評判都有各自的理由,著眼點(diǎn)不同,標(biāo)準(zhǔn)也就不同了。但是我們還是可以從它們的共性去考慮它們的復(fù)雜度。

字符串類模板通常都很輕量,不在本節(jié)討論范圍之內(nèi)。對于非字符串模板復(fù)雜度評判的共性標(biāo)準(zhǔn)是什么?我認(rèn)為,可以考量數(shù)據(jù)綁定的復(fù)雜度。

本文所指的數(shù)據(jù)綁定不只是插值,還包括上下文以及事件,甚至是整個運(yùn)行期的宿主環(huán)境。

事實(shí)上至少需要達(dá)到 VDOM 級別的引擎才具有這種能力,因?yàn)橥ㄟ^ VDOM 可以映射到真實(shí)的 DOM 節(jié)點(diǎn)。

大概有幾種模式(組合):

1.入口參數(shù)是個 Object,模板中的變量 x 是該對象的 .x 屬性,例:virtual-stache-example
2.特定語法或?qū)傩,比如:Vue.js 的 ...,屬性 computed、methods
3.抽象的語義化屬性,比如:Vue.js 的 active 這個詞適用于多種場景,容易理解且不產(chǎn)生歧義
4.不負(fù)責(zé)綁定,需要使用者非常熟悉原生方法,用原生方法進(jìn)行綁定,比如:PowJS

這些模式只是理論方面的,通常是模板引擎設(shè)計者要解決的問題。對于使用者來說不如直接問:

1.可以在 HTML 模板中直接寫最簡單的 console.log(context) 來調(diào)試么?
2.可以在多層 DOM 樹綁定或傳遞不同的上下文參數(shù)么?
3.可以在多層 DOM 樹內(nèi)層向上訪問已經(jīng)生成的 Node 么?

模板引擎團(tuán)隊會給你正確的解決辦法,但通常和問題字面描述的目標(biāo)有所差異。我覺得這就是你評判選擇的關(guān)鍵,你對官方給出的正確方法的認(rèn)可度。

嵌入到 DOM 中

嵌入到 HTML 中

PowJS 是這么實(shí)現(xiàn)的:

實(shí)現(xiàn)模板必須要實(shí)現(xiàn)的指令
預(yù)編譯輸出原生 ECMAScript 代碼
模板語法結(jié)構(gòu)與 ECMAScript 函數(shù)寫法一致
最終,寫 PowJS 模板就像在寫 ECMAScript 函數(shù)。

GoHub index 中的寫法

<template>
  <details func="repo" param="data" if="is.object(data.content)&&!sel(`#panel details[sha='${data.sha}']`)"
    open
    let="ctx=data.content"
    sha="{{data.sha}}"
    origin="{{ctx.Repo}}"
    repo="{{data.owner}}/{{data.repo}}"
    subdir="{{ctx.Subdir  ''}}"
    filename="{{ctx.Filename}}"
    render=":ctx"
    do="this.renew(sel(`#panel details[repo='${data.owner}/${data.repo}']`))"
    break
  >
    <summary>{{ctx.Description}}</summary>
    <p if="':';" each="ctx.Package,val-pkg">
      <p title="{{pkg.Progress}}: {{pkg.Synopsis}}">{{pkg.Import}}</p>
    </p>
  </details>
  <dl func="list" param="data"
    if="!sel(`#panel details[sha='${data.sha}']`)&&':'  '';"
    each="data.content,data.sha,val-rep"
    do="this.appendTo(sel('#panel'))">
    <details sha="{{sha}}" repo="{{rep.repo}}">
      <summary>{{rep.synopsis}}</summary>
    </details>
  </dl>
</template>

多數(shù)模板引擎都會實(shí)現(xiàn) if 、each 這些指令,上面的 PowJS 模板中還有:

全局對象 is、sel
模板(函數(shù))命名 repo 、list
模板(函數(shù))入口形參 data
自定義局部變量 ctx
下層模板(函數(shù))形參推導(dǎo) data.sha->sha
遍歷值到下層模板形參推導(dǎo) (ctx.Package,val-pkg)->pkg 、(data.content,val-rep)->rep
DOM 節(jié)點(diǎn)操作 this.renew、 this.appendTo,這直接渲染到頁面 DOM 樹
流程控制 break
偽節(jié)點(diǎn) if="':';",渲染時根本不生成 p 節(jié)點(diǎn),它是個偽節(jié)點(diǎn),相當(dāng)于塊代碼符號 "{}"
關(guān)鍵是整個模板結(jié)構(gòu),指令語義和 ECMAScript 函數(shù)完全一致:

沒有數(shù)據(jù)綁定,你寫的是 ECMAScript 函數(shù),傳參數(shù)好了,要什么綁定
沒有事件綁定,每個節(jié)點(diǎn)都是真實(shí)存在的,直接寫 addEventListener 就好了
要調(diào)試,隨便找個 do 或 if 或 let 插入 _=console.log(x), 就好了,逗號表達(dá)式幾乎可以無縫插入所有原生語句
所有的業(yè)務(wù)邏輯都是使用者自己寫的,PowJS 只負(fù)責(zé)把他們粘合成一個函數(shù)
導(dǎo)出視圖是 ECMAScript 源碼,下圖截取自演示 My Folders

665706216-5bc5a3886c089_articlex.png

那么 PowJS 是最終的選擇么?PowJS 的理念是原生性,原生的 DOM,原生的 ECMAScript。

原生也同樣是 PowJS 的問題所在,不是所有的使用者都喜歡原生,我相信有的使用者更喜歡更抽象風(fēng)格,他們眼中的原生總是帶了點(diǎn) "原始"。

原生意味著你可以擴(kuò)展,引入其它 library 進(jìn)行搭配,但 PowJS 永遠(yuǎn)不會出現(xiàn) define setter/getter實(shí)現(xiàn)的 watcher,那超出了模板引擎的范圍,如果有那一定是獨(dú)立的項目。

最后,我的觀點(diǎn)依然是:你的需求才是選擇模板的關(guān)鍵,適合你的才是好的。

以上就是如何選擇Web前端模板引擎(推薦)的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!


網(wǎng)站建設(shè)是一個廣義的術(shù)語,涵蓋了許多不同的技能和學(xué)科中所使用的生產(chǎn)和維護(hù)的網(wǎng)站。