Web Components入門(mén)教程
今天 ,Web 組件已經(jīng)從本質(zhì)上改變了HTML。初次接觸時(shí),它看起來(lái)像一個(gè)全新的技術(shù)。Web組件最初的目的是使開(kāi)發(fā)人員擁有擴(kuò)展瀏覽器標(biāo)簽的能力,可以自由的進(jìn)行定制組件。面對(duì)新的技術(shù),你可能會(huì)覺(jué)得無(wú)從下手。那這篇文章將為你揭開(kāi)Web組件神秘的面紗。如果你已經(jīng)熟知HTML標(biāo)簽和DOM編程,已經(jīng)擁有了大量可用 的Web組件,那么你已經(jīng)是Web組件專(zhuān)家了。
Web組件的現(xiàn)狀
隨著各式各樣的用戶(hù)需求,瀏覽器的原生組件已經(jīng)無(wú)法滿足需求。Web組件也就變得越來(lái)越重要。
我們將以自定義一個(gè)傳統(tǒng)三方插件為例來(lái)介紹Web組件。
首先,需要引用插件的CSS和JavaScript資源:
- <link rel="stylesheet" type="text/css" href="my-widget.css" />
- <script src="my-widget.js"></script>
接下來(lái),我們需要向頁(yè)面中添加占位符。
- <div data-my-widget></div>
***,我們需要使用腳本來(lái)找到并且實(shí)例化這個(gè)占位符為Web組件。
- // 使用 jQuery 初始化組件
- $(function() {
- $('[data-my-widget]').myWidget();
- });
通過(guò)以上是三個(gè)基本步驟。已經(jīng)完成了在頁(yè)面中添加了自定義插件,但是瀏覽器無(wú)法確定自定義組件的生命周期,如果通過(guò)以下方式聲明則使自定義組件生命周期變得清晰了。
- el.innerHTML = '<div data-my-widget></div>';
因?yàn)檫@不是一個(gè)內(nèi)置的組件,我們現(xiàn)在必須手動(dòng)實(shí)例化新組件,
- $(el).find('[data-my-widget]').myWidget();
避免這種復(fù)雜設(shè)置方法的有效方式是完全抽象DOM交互。不過(guò),這個(gè)動(dòng)作也比較復(fù)雜,需要?jiǎng)?chuàng)建框架或者庫(kù)來(lái)自定義組件。
面臨的問(wèn)題
組件一旦被聲明,占位符已經(jīng)被替代為原生的HTML標(biāo)記:
- <div data-my-widget>
- <div class="my-widget-foobar">
- <input type="text" class="my-widget-text" />
- <button class="my-widget-button">Go</button>
- </div>
- </div>
這樣做的弊端是,自定義組件的標(biāo)記和普通HTML組件的標(biāo)記混雜在一起,沒(méi)有清晰的分割和封裝。這就不可避免的會(huì)出現(xiàn)命名及樣式等沖突。
Web組件的產(chǎn)生
隨著三方Web組件的發(fā)展,它已經(jīng)成為了Web開(kāi)發(fā)不可或缺的部分:
- <!—導(dǎo)入: -->
- <link rel="import" href="my-widget.html" />
- <!—使用:-->
- <my-widget />
在這個(gè)實(shí)例中,我們通過(guò)導(dǎo)入HTML來(lái)添加組件并且立即使用。
更重要的是,因?yàn)?lt;my-widget />是瀏覽器原生支持的組件,它直接掛在瀏覽器的生命周期中,允許我們像添加原生組件一樣添加三方組件。
- el.innerHTML = '<my-widget />';
- // 插件當(dāng)前已經(jīng)被實(shí)例化
當(dāng)查看這個(gè)組件的HTML 源碼,你會(huì)發(fā)現(xiàn)它僅僅是一個(gè)單一的標(biāo)簽。如果啟用瀏覽器Shadow DOM 特性,才可以查看標(biāo)簽內(nèi)的組件,你將會(huì)發(fā)現(xiàn)一些有趣的事情,
當(dāng)我們談?wù)揥eb組件時(shí),我們不是在談?wù)撘婚T(mén)新技術(shù)。Web組件最初的目的是給我們封裝能力,它可以通過(guò)自定義組件和Shadow DOM 技術(shù)來(lái)實(shí)現(xiàn)。所以,接下來(lái),我們將著重介紹下這兩項(xiàng)技術(shù)。介紹以上兩個(gè)技術(shù)之前,我們***先梳理下已知瀏覽器原生組件。
已知的HTML組件
我們知道組件可以通過(guò)HTML標(biāo)記或JavaScript來(lái)實(shí)例化:
使用標(biāo)記實(shí)例化:
- <input type="text" />
- document.createElement('input');
- el.innerHTML = '<input type="text" />';
使用JaveScript實(shí)例化:
- document.createElement('input')
- document.createElement('div')
添加帶有屬性的HTML標(biāo)簽:
- // 創(chuàng)建帶有屬性的input標(biāo)簽...
- el.innerHTML = '<input type="text" value="foobar" />';
- //這時(shí)value屬性已經(jīng)同步
- el.querySelector('input').value;
組件可以響應(yīng)屬性的變化:
- // 如果我們更改value 屬性值
- input.setAttribute('value', 'Foobar');
- //屬性值會(huì)立即更改
- input.value === 'Foobar'; // true
組件可以有內(nèi)部隱藏的DOM結(jié)構(gòu):
- <!—使用一個(gè)input實(shí)現(xiàn)復(fù)雜的日歷功能-->
- <input type="date" />
- // 盡管其內(nèi)部結(jié)構(gòu)比較復(fù)雜,但是已經(jīng)封裝成為一個(gè)組件
- dateInput.children.length === 0; // true
組件可以使用子組件:
- <!—可以給組件提供任意個(gè) 'option' 標(biāo)簽-->
- <select>
- <option>1</option>
- <option>2</option>
- <option>3</option>
- </select>
組件可以為其子組件提供樣式:
- dialog::backdrop {
- background: rgba(0, 0, 0, 0.5);
- }
***,組件可以有內(nèi)置樣式。和自定義插件不同,我們不需要為瀏覽器的原生控件引用CSS文件。
有了以上的了解,我們已經(jīng)具備了解Web組件的基礎(chǔ)。使用自定義組件和Shadow DOM,我們可以在我們的插件中定義所有這些標(biāo)準(zhǔn)行為。
#p#
自定義組件
注冊(cè)一個(gè)新組件也比較簡(jiǎn)單:
- // 'document.register' 返回一個(gè)構(gòu)造函器
- var MyElement = document.register('my-element');
你也許注意到上面的自定義組件名稱(chēng)包含一個(gè)連接符。這是為了確保自定義組件名稱(chēng)不和瀏覽器內(nèi)置組件不沖突。
現(xiàn)在<my-element />這個(gè)組件具備了原生組件的特性,
所以,自定義組件也同樣可以進(jìn)行普通的DOM操作:
- document.create('my-element');
- el.innerHTML = '<my-element />';
- document.create('my-element');
構(gòu)建自定義組件
當(dāng)前,這個(gè)自定義組件僅僅有框架,而沒(méi)有內(nèi)容,下面讓我們向其中添加一些內(nèi)容:
- //我們將提供'document.register'的第二個(gè)參數(shù):
- document.register('my-element', {
- prototype: Object.create(HTMLElement.prototype, {
- createdCallback: {
- value: function() {
- this.innerHTML = '<h1>ELEMENT CREATED!</h1>';
- }
- }
- })
- });
在這個(gè)例子中,我們?cè)O(shè)置自定義組件的prototype,使用Object.create 方法創(chuàng)建一個(gè)繼承于HTMLElement的對(duì)象。在這個(gè)方法中修改該組件的屬性 innerHTML。
我們定義了createdCallback方法,在每次聲明實(shí)例時(shí)調(diào)用。你同樣可以有選擇性的定義attributeChangedCallback、 enteredViewCallback 和leftViewCallback等方法。
目前為止我們實(shí)現(xiàn)了動(dòng)態(tài)修改自定義組件內(nèi)容的功能,我們?nèi)匀恍枰峁┳远x組件的封裝方法,用于隱藏其內(nèi)部組件。
#p#
使用Shadow DOM實(shí)現(xiàn)封裝
我們需要完善下createdCallback方法。本次,除了修改innerHTML之外,我們添加一些額外的操作:
- createdCallback: {
- value: function() {
- var shadow = this.createShadowRoot();
- shadow.innerHTML = '<h1>SHADOW DOM!</h1>';
- }
- }
在這個(gè)例子中, 你會(huì)注意到‘SHADOW DOM!’,但是查看源碼時(shí)你會(huì)發(fā)現(xiàn)只有空白的<my-element /> 標(biāo)簽而已。這里使用創(chuàng)建Shadow Root 方法替代了直接修改頁(yè)面。
Shadow Root中的任何組件,是肉眼可見(jiàn)的,但是和當(dāng)前頁(yè)面的樣式和DOM API相隔離。這樣就實(shí)現(xiàn)了自定義組件是一個(gè)獨(dú)立組件的假象。
添加“輕量級(jí)DOM”
目前為止,我們的自定義組件是空標(biāo)簽,但是如果向其中添加內(nèi)部組件會(huì)出現(xiàn)什么現(xiàn)象呢?
我們假設(shè)自定義組件包含的節(jié)點(diǎn)如下,
- <my-element>
- 這是一個(gè)輕量級(jí) DOM。
- <i>hello</i>
- <i>world</i>
- </my-element>
一旦針對(duì)于這個(gè)組件的 Shadow Root 被創(chuàng)建,它的子節(jié)點(diǎn)不再存在。我們這些隱藏的子節(jié)點(diǎn)封裝為輕量級(jí)DOM節(jié)點(diǎn)。
如果禁用了 Shadow DOM,上面這個(gè)例子僅僅會(huì)顯示為:這是一個(gè)輕量級(jí) DOM‘hello world’。
當(dāng)我們?cè)赾reatedCallback方法中設(shè)置 Shadow DOM后,我們可以使用新增內(nèi)容分配輕量級(jí)DOM組件到Shadow DOM 中。
- createdCallback: {
- value: function() {
- var shadow = this.createShadowRoot();
- // 子組件'i' 標(biāo)簽現(xiàn)在已經(jīng)消失了
- shadow.innerHTML =
- ‘輕量級(jí) DOM 中的 "i" 標(biāo)簽為: ' +
- '<content select="i" />';
- //現(xiàn)在,在 Shadow DOM 中只有 'i' 標(biāo)簽是可以見(jiàn)的。
- }
- }
封裝樣式
Shadow DOM 最重要的作用是創(chuàng)建了和當(dāng)前頁(yè)面隔離的Web組件,使Web組件不受當(dāng)前頁(yè)面樣式和JaveScript腳本的影響。
- createdCallback: {
- value: function() {
- var shadow = this.createShadowRoot();
- shadow.innerHTML =
- "<style>span { color: green }</style>" +
- "<span>I'm green</span>";
- }
- }
反之,在 Shadow DOM 中定義的樣式也不會(huì)影響之外的標(biāo)簽樣式。
- <my-element />
- <span>I'm not green</span>
#p#
揭露鉤子的秘密
當(dāng)隱藏自定義組件內(nèi)部標(biāo)記,有時(shí)也需要在當(dāng)前頁(yè)面對(duì)組件中的內(nèi)部特定組件進(jìn)行樣式設(shè)置。
例如,如果我們自定義一個(gè)日歷插件,在不允許用戶(hù)控制整個(gè)插件的情況下,允許最終用戶(hù)去定義按鈕的樣式。
這是其中的部分特性和偽組件:
- createdCallback: {
- value: function() {
- var shadow = this.createShadowRoot();
- shadow.innerHTML = 'Hello <em part="world">World</em>';
- }
- }
這是在當(dāng)前頁(yè)面設(shè)置自定義組件內(nèi)部組件樣式的方法:
- my-element::part(world) {
- color: green;
- }
這部分內(nèi)容介紹了封裝web組件的基本方式。Shadow DOM 是我們可以任意修改Web組件中的標(biāo)簽。在例子中,我們?cè)O(shè)置了“World”的樣式,但是使用者卻無(wú)法判斷它是<em>標(biāo)簽。
在你嘗試自定義Web組件之前,需要確保瀏覽器的相關(guān)特性已經(jīng)打開(kāi)。如果使用 Chrome,在 Chrome 中打開(kāi)chrome://flags ,并且開(kāi)啟“experimental Web Platform features”。
這僅僅是個(gè)開(kāi)始
所有本文中介紹的內(nèi)容,都是模擬一些簡(jiǎn)單的瀏覽器標(biāo)準(zhǔn)行為。我們已經(jīng)習(xí)慣于和原生的瀏覽器組件進(jìn)行交互,因此自定義組件的步驟并不是想象中的那個(gè)難。Web組件最終提供我們一種實(shí)現(xiàn)簡(jiǎn)單、一致、可復(fù)用、封裝和組合部件的方法,這是一個(gè)有意義的開(kāi)始。