南京做網(wǎng)站建設(shè)搭建的公司app推廣方案策劃
目錄
引言
演變過(guò)程
概述
使用方式
創(chuàng)建標(biāo)簽
定義標(biāo)簽
使用標(biāo)簽
獲取標(biāo)簽
異步定義標(biāo)簽
升級(jí)標(biāo)簽
完整案例
結(jié)語(yǔ)
相關(guān)代碼
參考文章
引言
隨著項(xiàng)目體量的增大,組件化和模塊化的優(yōu)勢(shì)也愈發(fā)明顯了,構(gòu)建可重復(fù)使用、獨(dú)立、可互操作的組件變得尤為重要,在JS中我們可以通過(guò)class和函數(shù)對(duì)代碼解耦,使某段代碼可以復(fù)用。在TS中我們也可以通過(guò)模塊對(duì)代碼進(jìn)行模塊化開(kāi)發(fā),在HTML頁(yè)面中同樣有一種技術(shù)可以實(shí)現(xiàn)獨(dú)立的、可復(fù)用的組件,這便是本篇文章講到的Web Components
Web Components主要包括Custom Elements、Shadow DOM、HTML Templates和JavaScript這四部分,在后文及后續(xù)的文章中我會(huì)詳細(xì)展開(kāi)說(shuō)說(shuō)
演變過(guò)程
在熟悉web組件之前,我們可以了解一下早期的開(kāi)發(fā)人員如何進(jìn)行業(yè)務(wù)組件復(fù)用的?
聊聊我使用過(guò)的三種復(fù)用方式
一是使用JQ的load()進(jìn)行ajax請(qǐng)求,將結(jié)果渲染到某個(gè)div或者組件中,以我原先公司的頁(yè)面為例子,可以將圖中畫(huà)框的部分分別在多個(gè)html頁(yè)面中實(shí)現(xiàn),然后使用JQ進(jìn)行請(qǐng)求加載到主頁(yè)的標(biāo)簽中,達(dá)到組件化效果,此時(shí)如果要傳遞參數(shù)則可以通過(guò)url或者共用localstorage等形式共享狀態(tài)
這樣做確實(shí)可以將某個(gè)頁(yè)面或者標(biāo)簽?zāi)K進(jìn)行復(fù)用,但是缺點(diǎn)也很明顯,一是頁(yè)面加載是異步的,需要通過(guò)ajax請(qǐng)求html文件的形式完成,二是傳參的方式僅限于query的方式,復(fù)雜的參數(shù)支持率較為薄弱
第二種是Iframe的方式,這種方式和jq的load類似,同樣擁有獨(dú)立上下文,可以在當(dāng)前環(huán)境使用自己的CSS和JS,并且在加載時(shí)獨(dú)立于主頁(yè)面。當(dāng)然這么做的缺點(diǎn)也是有的,和load函數(shù)一樣,它的頁(yè)面單獨(dú)加載和渲染多出了性能開(kāi)銷,以及異步加載問(wèn)題
最后一種是使用JS的代碼進(jìn)行動(dòng)態(tài)HTML拼接,介于其強(qiáng)大的兼容性,動(dòng)態(tài)HTML拼接在早期的前端開(kāi)發(fā)中被廣泛使用,并且能夠在絕大多數(shù)瀏覽器上良好運(yùn)行,比如:
function createCustomTag(tagName, text, attributes) {var tag = "<" + tagName;for (var attr in attributes) {if (attributes.hasOwnProperty(attr)) {tag += " " + attr + '="' + attributes[attr] + '"';}}tag += ">" + text + "</" + tagName + ">";return tag;
}
這么做的好處是兼容性高 ,靈活性強(qiáng),原生JS即可支持;但是其缺點(diǎn)是使JS語(yǔ)法以及CSS樣式的隔離變得困難,代碼可讀性和可維護(hù)性降低,最終也被摒棄。
那么回到新生代的web components,它能解決什么問(wèn)題,又有什么注意點(diǎn)?感興趣的話就接著往下看吧
概述
自定義元素(Custom Elements)在許多框架和UI組件中廣泛使用,比如elementUI:?<el-xxx></el-xxx>;vant:<van-xxx />等。開(kāi)發(fā)者可以通過(guò)創(chuàng)建自定義的HTML元素,使其在頁(yè)面中表現(xiàn)和使用類似于內(nèi)置的HTML元素。開(kāi)發(fā)者通過(guò)自定義元素創(chuàng)建自定義的HTML元素,使其在頁(yè)面中表現(xiàn)和使用類似于內(nèi)置的HTML元素,它是通過(guò) JavaScript 創(chuàng)建一個(gè)自定義元素類,并通過(guò)繼承HTMLElement類來(lái)定義其行為和樣式。
使用方式
創(chuàng)建標(biāo)簽
首先是創(chuàng)建標(biāo)簽,通過(guò)繼承HTMLElement類的方式來(lái)創(chuàng)建自定義標(biāo)簽的行為和樣式。
在構(gòu)造函數(shù)中可以進(jìn)行自定義標(biāo)簽的初始化工作,例如設(shè)置默認(rèn)屬性、添加事件監(jiān)聽(tīng)器等
class MyCustomElement extends HTMLElement {constructor() {super();this.textContent = "my-custom-element"// 自定義元素被創(chuàng)建時(shí)的初始化邏輯}
}
此外自定義標(biāo)簽類可以包含以下幾種函數(shù):
- 連接回調(diào)(connectedCallback):自定義元素被插入到DOM樹(shù)中時(shí)調(diào)用
- 斷開(kāi)回調(diào)(disconnectedCallback):自定義元素從DOM樹(shù)中刪除時(shí)調(diào)用
- 移動(dòng)回調(diào)(adoptedCallback):當(dāng)自定義元素被移動(dòng)到新文檔時(shí)調(diào)用
- 屬性變化回調(diào)(attributeChangedCallback):自定義元素的屬性被添加、刪除或修改時(shí)調(diào)用
- 靜態(tài)屬性(observedAttributes):指定attributeChangedCallback要監(jiān)聽(tīng)哪些屬性的數(shù)組
使用示例可以參考以下代碼(具體效果及用法會(huì)在后文貼出)
class MyCustomElement extends HTMLElement {constructor() {super();// 自定義元素被創(chuàng)建時(shí)的初始化邏輯this.textContent = "my-custom-element"}connectedCallback() {// 元素被插入到DOM時(shí)調(diào)用console.log("元素被插入到DOM");}disconnectedCallback() {// 元素從DOM中移除時(shí)調(diào)用console.log("元素從DOM中移除");}adoptedCallback() {// 元素被移動(dòng)到新文檔時(shí)調(diào)用console.log("元素移動(dòng)到新文檔");}attributeChangedCallback(attrName, oldValue, newValue) {// 元素的屬性被添加、刪除或修改時(shí)調(diào)用console.log(`${attrName}屬性的舊值:${oldValue},新值:${newValue}`);}// 或使用靜態(tài)屬性代替get方法static get observedAttributes() {// 指定要監(jiān)聽(tīng)的元素的屬性數(shù)組return ['name', 'date'];}
}
定義標(biāo)簽
在創(chuàng)建標(biāo)簽后,我們需要通過(guò)customElements.define來(lái)定義一個(gè)自定義標(biāo)簽
customElements.define("my-custom-element", MyCustomElement)
tips:需要注意的是創(chuàng)建的標(biāo)簽的中間必須帶 '-'?短橫線,與原生標(biāo)簽隔開(kāi),比如:custom-element,而像:customElement,-customElement,customElement-,這幾種是無(wú)法作為自定義名稱使用的。
define函數(shù)可以傳入三個(gè)參數(shù),第三個(gè)可選參數(shù)是ElementDefinitionOptions,這個(gè)參數(shù)在使用自定義元素繼承時(shí)才會(huì)用到,當(dāng)我們定義一個(gè)自定義元素時(shí),可以選擇讓它繼承自內(nèi)置的 HTML 元素。參考下面的代碼
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>CustomElements</title>
</head><body><button is="my-custom-button"></button><script>customElements.define("my-custom-button", class extends HTMLButtonElement {constructor() {super()this.textContent = "按鈕";}connectedCallback() {this.addEventListener("click", () => console.log("點(diǎn)擊了"))}}, { extends: "button" })// 繼承自原生的按鈕標(biāo)簽</script>
</body></html>
需要注意的是, 自定義標(biāo)簽類同樣需要繼承于對(duì)應(yīng)的HTML標(biāo)簽類(比如:HTMLButtonElement),其次在標(biāo)簽中使用時(shí)需要增加is屬性,相當(dāng)于是對(duì)button進(jìn)行拓展
使用標(biāo)簽
使用自定義元素的方式有兩種,分別是通過(guò)document.createElement('my-custom-element')和直接在頁(yè)面中使用<my-custom-element></my-custom-element>標(biāo)簽,這和原生語(yǔ)法一致,只需要把常用的div,a,span等標(biāo)簽換成自定義的標(biāo)簽即可
<body><my-custom-element>my-custom-element</my-custom-element><script>const ele = document.createElement("my-custom-element")ele.textContent = "my-custom-element"document.body.appendChild(ele)</script>
</body>
獲取標(biāo)簽
通過(guò)customElements.get(elemName)可以獲取標(biāo)簽為elemName的自定義標(biāo)簽類,如果在定義之前獲取則顯示未定義
console.log(customElements.get(elemName));// undefined
customElements.define(elemName, MyCustomElement)
console.log(customElements.get(elemName).name);// class MyCustomElement extends HTMLElement {...}
異步定義標(biāo)簽
使用customElements.whenDefined(elemName)函數(shù)可以在標(biāo)簽定義時(shí)觸發(fā)回調(diào)函數(shù)
setTimeout(() => {customElements.define(elemName, MyCustomElement)
}, 1000)
customElements.whenDefined(elemName).then(console.log)// class MyCustomElement
console.log(customElements.get(elemName));// undefined
升級(jí)標(biāo)簽
升級(jí)標(biāo)簽的目的是將自定義標(biāo)簽(ele)和自定義標(biāo)簽的構(gòu)造函數(shù)或類(MyCustomElement)進(jìn)行綁定,使標(biāo)簽(ele)可以訪問(wèn)類(MyCustomElement)的屬性及方法。通過(guò)customElements.upgrade(ele)函數(shù)可以對(duì)自定義標(biāo)簽進(jìn)行升級(jí)操作
class MyCustomElement extends HTMLElement {bgColor = "red"constructor() {super();}
}
const elemName = "my-custom-element"
const ele = document.createElement(elemName)
customElements.define(elemName, MyCustomElement)
console.log(Reflect.ownKeys(ele), ele.bgColor);// [] undefined
customElements.upgrade(ele);// 升級(jí)ele,使其與自定義標(biāo)簽類綁定,也就是說(shuō)可以訪問(wèn)MyCustomElement的屬性及方法
console.log(Reflect.ownKeys(ele), ele.bgColor);// ['bgColor'] red
完整案例
最后我們結(jié)合上面的知識(shí)點(diǎn),實(shí)現(xiàn)一個(gè)完整的自定義標(biāo)簽的示例
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>CustomElements</title>
</head><body><iframe src="./temp.html" width="100" height="100"></iframe><script>const elemName = "my-custom-element"const ele = document.createElement(elemName)const iframeEle = document.querySelector("iframe")class MyCustomElement extends HTMLElement {bgColor = "red"constructor() {super();// 自定義元素被創(chuàng)建時(shí)的初始化邏輯this.textContent = elemName}connectedCallback() {// 元素被插入到DOM時(shí)調(diào)用console.log("元素被插入到DOM");}disconnectedCallback() {// 元素從DOM中移除時(shí)調(diào)用console.log("元素從DOM中移除");}adoptedCallback() {// 元素被移動(dòng)到新文檔時(shí)調(diào)用console.log("元素移動(dòng)到新文檔");}attributeChangedCallback(attrName, oldValue, newValue) {// 元素的屬性被添加、刪除或修改時(shí)調(diào)用console.log(`${attrName}屬性的舊值:${oldValue},新值:${newValue}`);}// 或使用靜態(tài)屬性代替get方法static get observedAttributes() {// 指定要監(jiān)聽(tīng)的元素的屬性數(shù)組return ['name', 'date'];}}customElements.whenDefined(elemName).then(() => {const tempBox = iframeEle.contentDocument // 獲取iframe的domdocument.body.appendChild(ele)// 元素被插入到DOMele.setAttribute("name", elemName)// name屬性的舊值:null,新值:my-custom-elementele.setAttribute("date", "date")// date屬性的舊值:null,新值:dateele.setAttribute("name", "my-custom-element2")// name屬性的舊值:my-custom-element,新值:my-custom-element2ele.remove()// 元素從DOM中移除// 元素移動(dòng)到新文檔,通過(guò)iframe進(jìn)行舉例tempBox.body.appendChild(tempBox.adoptNode(ele))// 元素被插入到DOM})// 異步檢查自定義標(biāo)簽定義,標(biāo)簽定義時(shí)觸發(fā)該函數(shù)console.log(customElements.get(elemName));// 獲取自定義標(biāo)簽,此時(shí)未定義console.log(ele instanceof MyCustomElement); // falseiframeEle.onload = () => {setTimeout(() => {// 加個(gè)setTimeout明顯一點(diǎn)customElements.define(elemName, MyCustomElement)console.log(Reflect.ownKeys(ele), ele.bgColor);// [] undefinedcustomElements.upgrade(ele);// 升級(jí)ele,使其與自定義標(biāo)簽類綁定,也就是說(shuō)可以訪問(wèn)MyCustomElement的屬性及方法console.log(Reflect.ownKeys(ele), ele.bgColor);// ['bgColor'] redconsole.log(ele instanceof MyCustomElement);// trueconsole.log(customElements.get(elemName).name);// MyCustomElement , 獲取自定義標(biāo)簽,此時(shí)已定義}, 1000);}</script>
</body></html>
上述代碼展示了一個(gè)簡(jiǎn)單的自定義元素的定義和使用過(guò)程,包括自定義元素的構(gòu)造函數(shù)、移動(dòng)、生命周期回調(diào)方法以及屬性變化回調(diào)方法的使用,以及customElements中的幾種方法
結(jié)語(yǔ)
Custom Elements允許我們創(chuàng)建自定義的HTML元素,使其在頁(yè)面中表現(xiàn)和使用類似于內(nèi)置的HTML元素。我們可以通過(guò)繼承HTMLElement類來(lái)定義自定義元素的行為和樣式,并在構(gòu)造函數(shù)中進(jìn)行初始化工作。通過(guò)類的機(jī)制,我們可以達(dá)到復(fù)用自定義元素的目的。后面的系列文章我會(huì)基于其強(qiáng)大的Api與Shadow DOM和HTML Templates實(shí)現(xiàn)組件化效果,敬請(qǐng)期待。
文章到這也就結(jié)束了,感謝你的閱讀,如果覺(jué)得文章對(duì)你有幫助的話,還望三連支持一下作者,感謝!
相關(guān)代碼
WebComponents/CustomElements.html · 阿宇的編程之旅/myCode - Gitee.com
參考文章
Web components
Web Component - Web API 接口參考 | MDN