標準化用戶端範本
簡介
範本的概念並不是網頁程式開發的概念。事實上,Django (Python)、ERB/Haml (Ruby) 和 Smarty (PHP) 等伺服器端範本語言/引擎已存在很久。不過,過去幾年來,我們看到 MVC 架構不斷爆炸。這些方法都略有不同,但在呈現展示層 (亦稱為「資料檢視」) 的常見機制:範本。
面對現實,範本非常出色。問問自己吧連定義都讓你感到溫馨又舒適:
「...不需要每次都重新建立...」雖然不認識您,但我喜歡避免多餘的處理工作。為何網路平台對開發人員顯然關心的事物缺乏原生支援?
WhatWG HTML 範本規格是解答。該定義會定義新的 <template>
元素,用於描述用戶端範本的標準 DOM 式方法。範本可讓您宣告標記片段片段,這些片段會剖析為 HTML、在網頁載入時不使用,但之後可以在執行階段進行例項化。引述:Rafael Weinstein:
他們要在這裡放置大量 HTML 程式碼,不希望瀏覽器出於任何理由。
Rafael Weinstein (規格作者)
特徵偵測
如要偵測 <template>
,請建立 DOM 元素,並檢查 .content
屬性是否存在:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
宣告範本內容
HTML <template>
元素代表標記中的範本。其中包含「範本內容」,基本上就是插入可複製 DOM 的區塊。範本就像是鷹架元件,可以在應用程式的生命週期內使用 (並重複使用)。
如要建立範本內容,請宣告一些標記並納入 <template>
元素中:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
重點特色
將內容納入 <template>
提供了幾項重要屬性。
內容在啟用前無法有效插入。基本上,標記為隱藏 DOM,且不會轉譯。
範本中的內容都不會產生副作用。指令碼無法執行、無法載入圖片、不會播放音訊,而是必須先使用範本。
系統不會將內容視為不在文件中。在主頁面中使用
document.getElementById()
或querySelector()
不會傳回範本的子節點。範本可以放在
<head>
、<body>
或<frameset>
內的任何位置,且可包含這些元素中允許的任何類型的內容。請注意,「任何位置」代表<template>
可以安全用於 HTML 剖析器不允許...全部、內容模型子項。它也可以做為<table>
或<select>
的子項:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
啟用範本
如要使用某個範本,您需要啟用該範本。否則系統一律不會顯示其內容。最簡單的做法是使用 document.importNode()
建立其 .content
的深度副本。.content
屬性是內含範本 gut 的唯讀 DocumentFragment
。
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
蓋完範本後,範本的內容就會「上線」。在這個特定範例中,系統會複製內容、建立圖片要求,並轉譯最終的標記。
試聽帶
範例:插入指令碼
這個範例會說明範本內容的傳播程度。只有在使用者按下按鈕時,將範本蓋過蓋章時,<script>
才會執行。
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true)
);
}
</script>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
範例:使用範本建立 Shadow DOM
大多數使用者會將標記字串設為 .innerHTML
,藉此將 Shadow DOM 附加至主機:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
這種做法的問題是 Shadow DOM 取得越複雜,您使用的字串越多。也不會擴大規模
使遊戲混亂快速傳動此方法也是 XSS 最初的誕生方式!<template>
即可還原。
更嚴謹的做法是將範本內容附加至陰影根目錄,以便直接使用 DOM:
<template>
<style>
:host {
background: #f8f8f8;
padding: 10px;
transition: all 400ms ease-in-out;
box-sizing: border-box;
border-radius: 5px;
width: 450px;
max-width: 100%;
}
:host(:hover) {
background: #ccc;
}
div {
position: relative;
}
header {
padding: 5px;
border-bottom: 1px solid #aaa;
}
h3 {
margin: 0 !important;
}
textarea {
font-family: inherit;
width: 100%;
height: 100px;
box-sizing: border-box;
border: 1px solid #aaa;
}
footer {
position: absolute;
bottom: 10px;
right: 5px;
}
</style>
<div>
<header>
<h3>Add a Comment
</header>
<content select="p"></content>
<textarea></textarea>
<footer>
<button>Post</button>
</footer>
</div>
</template>
<div id="host">
<p>Instructions go here</p>
</div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.appendChild(document.querySelector('template').content);
</script>
我瞭解了
我在公開使用 <template>
時可能會遇到以下問題:
- 如果使用的是modpagespeed,請留意這個錯誤。定義內嵌
<style scoped>
的範本可透過 PageSpeed 的 CSS 重新編寫規則移至標題。 - 無法「預先算繪」範本,也就是說,伺服器和用戶端無法預先載入資產、處理 JS、下載初始 CSS 等。範本只有在上線時才會顯示。
請謹慎使用巢狀範本。但實際上的行為不如預期。例如:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
啟用外部範本並不會啟用內部範本。也就是說,巢狀範本需要手動啟用其子項。
邁向標準之路
讓我們記不得一下我們的是從哪裡來的。標準 HTML 範本問世以來都是很長的路多年來,我們不斷想出一些聰明的技巧 來建立可重複使用的範本以下是我常遇到的兩個常見情況。 我們在本文中列出了這些資料欄,以便進行比較。
方法 1:畫面外 DOM
長久以來,人們一直使用一種方法,就是建立「畫面外」DOM,並使用 hidden
屬性或 display:none
將其隱藏在檢視畫面中。
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
雖然這項技巧有效,但仍有一些缺點。這項技巧的總結:
- 使用 DOM - 瀏覽器瞭解 DOM。你真的很擅長。我們可以輕鬆複製該網址。
- 未顯示任何內容 - 加入
hidden
會導致區塊無法顯示。 - Not inert - 即使內容處於隱藏狀態,系統仍會對圖片發出網路要求。
- 簡易樣式和主題設定 - 嵌入頁面必須在所有 CSS 規則前面加上
#mytemplate
前置字串,才能將樣式範圍縮小至範本。這種狀況很脆弱,而且無法保證日後不會遇到命名衝突。例如,如果嵌入的網頁已有使用該 ID 的元素,我們就會改善該元素。
方法 2:超載指令碼
另一項技術就是超載 <script>
,並以字串形式操控內容。John Resig 可能是 2008 年首度與他的 Micro Templating 公用程式分享這類資訊。現在還有很多新孩子,例如 handlebars.js 等街區。
例如:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
這項技巧的總結:
- 未顯示任何內容 - 由於
<script>
預設為display:none
,瀏覽器不會轉譯這個區塊。 - Inert - 瀏覽器不會將指令碼內容剖析為 JS,因為其類型設為「text/javascript」以外的內容。
- 安全性問題:建議使用
.innerHTML
。 在使用者提供的資料期間執行字串剖析,很容易導致 XSS 安全漏洞。
結論
還記得何時使用 jQuery 擺脫 DOM 的簡易操作嗎?結果是 querySelector()
/querySelectorAll()
新增至平台。贏了,對吧?這個程式庫常用以使用 CSS 選取器和標準擷取 DOM,後來採用。不是每次都這樣有效,而是我愛。
我認為 <template>
也是一樣。它可以標準化用戶端範本的方式,但更重要的是,它可省去 2008 個入侵的麻煩。在我的書籍中,讓整個網頁編寫程序變得更簡單、更易於維護,且更完整功能一直是一件好事。
其他資源
- WhatWG 規格
- 網頁元件簡介
- <web>components</web> (影片) - 為你本人進行最全面的簡報。