前言
?最近加入到新項目組負(fù)責(zé)前端技術(shù)預(yù)研和選型,一直偏向于以Polymer為代表的WebComponent技術(shù)線,于是查閱各類資料想說服老大向這方面靠,最后得到的結(jié)果是:"資料99%是英語無所謂,最重要是UI/UX上符合要求,技術(shù)的事你說了算。",于是我只好乖乖地去學(xué)UI/UX設(shè)計的事,木有設(shè)計師撐腰的前端是苦逼的:(嘈吐一地后,還是擠點時間總結(jié)一下WebComponent的內(nèi)容吧,為以后作培訓(xùn)材料作點準(zhǔn)備。
浮在水面上的痛
組件噪音太多了!
?在使用Bootstrap的Modal組件時,我們不免要Ctrl+c然后Ctrl+v下面一堆代碼
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

?一個不留神誤刪了一個結(jié)束標(biāo)簽,或拼錯了某個class或?qū)傩阅蔷捅吡?,此時一個語法高亮、提供語法檢查的編輯器是如此重要??!但是我其實只想配置個Modal而已。
?由于元素信息由
標(biāo)簽標(biāo)識符,元素特性和樹層級結(jié)構(gòu)組成,所以排除噪音后提取的核心配置信息應(yīng)該如下(YAML語法描述):
dialog:
modal: true
children:
header:
title: Modal title
closable: true
body:
children:
p:
textContent: One fine body…
footer
children:
button:
type: close
textContent: Close
button:
type: submit
textContent: Save changes
轉(zhuǎn)換成HTML就是
<dialog modal>
<dialog-header title="Modal title" closable></dialog-header>
<dialog-body>
<p>One fine body…</p>
</dialog-body>
<dialog-footer>
<dialog-btn type="close">Close</dialog-btn>
<dialog-btn type="submit">Save changes</dialog-btn>
</dialog-footer>
</dialog>
而像Alert甚至可以極致到這樣
<alert>是不是很簡單???</alert>
可惜瀏覽器木有提供<alert></alert>,那怎么辦呢?
手打牛丸模式1
既然瀏覽器木有提供,那我們自己手寫一個吧!
<script>
'use strict'
class Alert{
constructor(el = document.createElement('ALERT')){
this.el = el
const raw = el.innerHTML
el.dataset.resolved = ''
el.innerHTML = `<div class="alert alert-warning alert-dismissible fade in">
<button type="button" class="close" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
${raw}
</div>`
el.querySelector('button.close').addEventListener('click', _ => this.close())
}
close(){
this.el.style.display = 'none'
}
show(){
this.el.style.display = 'block'
}
}
function registerElement(tagName, ctorFactory){
[...document.querySelectorAll(`${tagName}:not([data-resolved])`)].forEach(ctorFactory)
}
function registerElements(ctorFactories){
for(let k in ctorFactories){
registerElement(k, ctorFactories[k])
}
}
清爽一下!
<alert>舒爽多了!</alert>
<script>
registerElements({alert: el => new Alert(el)})
</script>
復(fù)盤找問題
?雖然表面上實現(xiàn)了需求,但存在2個明顯的缺陷
- 不完整的元素實例化方式
原生元素有2種實例化方式
a. 聲明式
<!-- 由瀏覽器自動完成 元素實例化 和 添加到DOM樹 兩個步驟 -->
<input type="text">
b. 命令式
// 元素實例化
const input = new HTMLInputElement() // 或者 document.createElement('INPUT')
input.type = 'text'
// 添加到DOM樹
document.querySelector('#mount-node').appendChild(input)
?由于聲明式注重What to do,而命令式注重How to do,并且我們操作的是DOM,所以采用聲明式的HTML標(biāo)簽比命令式的JavaScript會來得簡潔平滑。但當(dāng)我們需要動態(tài)實例化元素時,命令式則是最佳的選擇。于是我們勉強可以這樣
// 元素實例化
const myAlert = new Alert()
// 添加到DOM樹
document.querySelector('#mount-node').appendChild(myAlert.el)
/*
由于Alert無法正常實現(xiàn)HTMLElement和Node接口,因此無法實現(xiàn)
document.querySelector('#mount-node').appendChild(myAlert)
myAlert和myAlert.el的差別在于前者的myAlert是元素本身,而后者則是元素句柄,其實沒有明確哪種更好,只是原生方法都是支持操作元素本身,一下來個不一致的句柄不蒙才怪了
*/
?即使你能忍受上述的代碼,那通過innerHTML實現(xiàn)半聲明式的動態(tài)元素實例化,那又怎么玩呢?是再手動調(diào)用一下registerElement('alert', el => new Alert(el))嗎?
?更別想通過document.createElement來創(chuàng)建自定義元素了。
- 有生命無周期
?元素的生命從實例化那刻開始,然后經(jīng)歷如添加到DOM樹、從DOM樹移除等階段,而想要更全面有效地管理元素的話,那么捕獲各階段并完成相應(yīng)的處理則是唯一有效的途徑了。
生命周期很重要
?當(dāng)定義一個新元素時,有3件事件是必須考慮的:
- 元素自閉合: 元素自身信息的自包含,并且不受外部上下文環(huán)境的影響;
- 元素的生命周期: 通過監(jiān)控元素的生命周期,從而實現(xiàn)不同階段完成不同任務(wù)的目錄;
- 元素間的數(shù)據(jù)交換: 采用property in, event out的方式與外部上下文環(huán)境通信,從而與其他元素進行通信。
?元素自閉合貌似無望了,下面我們試試監(jiān)聽元素的生命周期吧!
手打牛丸模式2
?通過constructor我們能監(jiān)聽元素的創(chuàng)建階段,但后續(xù)的各個階段呢?可幸的是可以通過MutationObserver監(jiān)聽document.body來實現(xiàn):)
最終得到的如下版本:
'use strict'
class Alert{
constructor(el = document.createElement('ALERT')){
this.el = el
this.el.fireConnected = () => { this.connectedCallback && this.connectedCallback() }
this.el.fireDisconnected = () => { this.disconnectedCallback && this.disconnectedCallback() }
this.el.fireAttributeChanged = (attrName, oldVal, newVal) => { this.attributeChangedCallback && this.attributeChangedCallback(attrName, oldVal, newVal) }
const raw = el.innerHTML
el.dataset.resolved = ''
el.innerHTML = `<div class="alert alert-warning alert-dismissible fade in">
<button type="button" class="close" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
${raw}
</div>`
el.querySelector('button.close').addEventListener('click', _ => this.close())
}
close(){
this.el.style.display = 'none'
}
show(){
this.el.style.display = 'block'
}
connectedCallback(){
console.log('connectedCallback')
}
disconnectedCallback(){
console.log('disconnectedCallback')
}
attributeChangedCallback(attrName, oldVal, newVal){
console.log('attributeChangedCallback')
}
}
function registerElement(tagName, ctorFactory){
[...document.querySelectorAll(`${tagName}:not([data-resolved])`)].forEach(ctorFactory)
}
function registerElements(ctorFactories){
for(let k in ctorFactories){
registerElement(k, ctorFactories[k])
}
}
const observer = new MutationObserver(records => {
records.forEach(record => {
if (record.addedNodes.length && record.target.hasAttribute('data-resolved')){
// connected
record.target.fireConnected()
}
else if (record.removedNodes.length){
// disconnected
const node = [...record.removedNodes].find(node => node.hasAttribute('data-resolved'))
node && node.fireDisconnected()
}
else if ('attributes' === record.type && record.target.hasAttribute('data-resolved')){
// attribute changed
record.target.fireAttributeChanged(record.attributeName, record.oldValue, record.target.getAttribute(record.attributeName))
}
})
})
observer.observe(document.body, {attributes: true, childList: true, subtree: true})
registerElement('alert', el => new Alert(el))
總結(jié)
?千辛萬苦擼了個基本不可用的自定義元素模式,但通過代碼我們進一步了解到對于自定義元素我們需要以下基本特性:
- 自定義元素可通過原有的方式實例化(
<custom-element></custom-element>,new CustomElement()和document.createElement('CUSTOM-ELEMENT')) - 可通過原有的方法操作自定義元素實例(如
document.body.appendChild等) - 能監(jiān)聽元素的生命周期
下一篇《WebComponent魔法堂:深究Custom Element 之 標(biāo)準(zhǔn)構(gòu)建》中,我們將一同探究H5標(biāo)準(zhǔn)中Custom Element API,并利用它來實現(xiàn)滿足上述特性的自定義元素:)
?尊重原創(chuàng),轉(zhuǎn)載請注明來自: http://www.cnblogs.com/fsjohnhuang/p/5918677.html _肥仔John