Web Components
首先來了解下 Web Components 的基本概念, Web Component 是指一系列加入 w3c 的 HTML與DOM的特性,目的是為了從原生層面實現(xiàn)組件化,可以使開發(fā)者開發(fā)、復(fù)用、擴展自定義組件沐兵,實現(xiàn)自定義標(biāo)簽。
這是目前前端開發(fā)的一次重大的突破便监。它意味著我們前端開發(fā)人員開發(fā)組件時扎谎,不必關(guān)心那些其他MV框架的兼容性,真正可以做到 “Write once, run anywhere”烧董。*
例如:
// 假如我已經(jīng)構(gòu)建好一個 Web Components 組件 <hello-world>并導(dǎo)出
// 在 html 頁面毁靶,我們就可以直接引用組件
<script src="/my-component.js"></script>
// 而在 html 里面我們可以這樣使用
<hello-world></hello-word>
而且跟任何框架無關(guān),代表著它不需要任何外部 runtime 的支持逊移,也不需要復(fù)雜的Vnode算法映射到實際DOM预吆,只是瀏覽器api本身對標(biāo)簽內(nèi)部邏輯進行一些編譯處理,性能必定會比一些MV*框架要好一些胳泉。
那它是怎么做到高性能的呢拐叉?主要和它的核心API有關(guān)。其實在上篇中我們已經(jīng)簡單提到了 Web Components 的三個核心 API扇商,接下來我?guī)Т蠹以敿?xì)分析各個api所承擔(dān)的功能和實際用法凤瘦,想必了解過 Web Component 核心技術(shù)后,大家就不會對它感到陌生了钳吟。
三個核心API
Custom elements(自定義元素)
首先來了解下自定義元素廷粒,其實它是作為 Web Component 的基石窘拯。那么我們來看下這個基石提供了哪些方法红且,提供給我們進行高樓大廈的建設(shè)坝茎。
- 自定義元素掛載方法
自定義元素通過CustomElementRegistry 來自定義可以直接渲染的html元素,掛載在 window.customElements.define 來供開發(fā)者調(diào)用暇番,demo 如下:
// 假如我已經(jīng)構(gòu)建好一個 Web Components 組件 <hello-world>并導(dǎo)出
class HelloWorld extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 10px;
background-color: #eee;
}
</style>
<h1>Hello World!</h1>
`;
}
}
// 掛載
window.customElements.define('hello-world', HelloWorld)
// 然后就可以在 html 中使用
<hello-world></hello-world>
注意:自定義元素必須用'-'連接符連接嗤放,來作為特定的區(qū)分,如果沒有檢測到自定義元素壁酬,則瀏覽器會作為空div處理次酌。
渲染結(jié)果:
- 自定義元素的類
由上面的例子 "class HelloWorld extends HTMLElement { xxx } " 發(fā)現(xiàn),自定義元素的構(gòu)造都是基于 HTMLElement舆乔,所以它繼承了 HTML 元素特性岳服,當(dāng)然,也可以繼承 HTMLElement的派生類希俩,如:HTMLButtonElement 等吊宋,來作為現(xiàn)有標(biāo)簽的擴展。
- 自定義元素的生命周期
類似于現(xiàn)有MV*框架的生命周期颜武,自定義元素的基類里面也包含了完整的生命周期 hook 來提供給開發(fā)者實現(xiàn)一些業(yè)務(wù)邏輯的應(yīng)用:
class HelloWorld extends HTMLElement {
constructor() {
// 1 構(gòu)建組件的時候的邏輯 hook
super();
}
// 2 當(dāng)自定義元素首次被渲染到文檔時候調(diào)用
connectedCallback(){
}
// 3 當(dāng)自定義元素在文檔中被移除調(diào)用
disconnectedCallback(){
}
// 4 當(dāng)自定義組件被移動到新的文檔時調(diào)用
adoptedCallback(){
}
// 5 當(dāng)自定義元素的屬性更改時調(diào)用
attributeChangedCallback(){
}
}
- 添加自定義方法和屬性
由于自定義元素由一個類來構(gòu)造璃搜,所以添加自定義屬性和方法就如同平常開發(fā)類的方法一致。
class HelloWorld extends HTMLElement {
constructor() {
super();
}
tag = 'hello-world'
say(something: string) {
console.log(`hello world, I want to say ${this.tag} ${something}`)
}
}
// 調(diào)用方法如下
const hw = document.querySelector('hello-world');
hw.say('good');
// 控制臺打印效果如下
Shadow DOM(影子DOM)
有了自定義元素作為基石鳞上,我們想要更加順暢的進行組件化封裝这吻,必定少不了對于DOM樹的操作。那么好的篙议,Shadow DOM(影子DOM)就應(yīng)運而生了唾糯。
顧名思義,影子DOM就是用來隔離自定義元素不受到外界樣式或者一些副作用的影響涡上,或者內(nèi)部的一些特性不會影響外部趾断。使自定義元素保持一個相對獨立的狀態(tài)。
在我們?nèi)粘i_發(fā)html頁面的時候也會接觸到一些使用 Shadow DOM 的標(biāo)簽吩愧,比如:audio 和 video 等芋酌;在具體dom樹中它會一一個標(biāo)簽存在,會隱藏內(nèi)部的結(jié)構(gòu)雁佳,但是其中的控件脐帝,比如:進度條、聲音控制等糖权,都會以一個Shadow DOM存在于標(biāo)簽內(nèi)部堵腹,如果想要查看具體的DOM結(jié)構(gòu),則可以嘗試在chrome的控制臺 -> Preferences -> Show user agent Shadow DOM星澳, 就可以查看到內(nèi)部的結(jié)構(gòu)構(gòu)成疚顷。
如果組件使用Shadow host,常規(guī)document中會存在一個 Shadow host節(jié)點用來掛載 Shadow DOM,Shadow DOM內(nèi)部也會存在一個DOM樹:Shadow Tree腿堤,根節(jié)點為Shadow root阀坏,外部可以用偽類:host來訪問,Shadow boundary其實就是Shadow DOM的邊界笆檀。具體架構(gòu)圖如下:
下面我們通過一個簡單的例子來看下Shadow DOM的實際用處:
// Shadow DOM 開啟方式為
this.attachShadow( { mode: 'open' } );
- 不使用Shadow DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components</title>
<style> h1 {
font-size: 20px;
color: yellow;
} </style>
</head>
<body>
<div></div>
<hello-world></hello-world>
<h1>Hello World! 外部</h1>
<script type="module"> class HelloWorld extends HTMLElement {
constructor() {
super();
// 關(guān)閉 shadow DOM
// this.attachShadow({ mode: 'open' });
const d = document.createElement('div');
const s = document.createElement('style');
s.innerHTML = `h1 {
display: block;
padding: 10px;
background-color: #eee;
}`
d.innerHTML = `
<h1>Hello World! 自定義組件內(nèi)部</h1>
`;
this.appendChild(s);
this.appendChild(d);
}
tag = 'hello-world'
say(something) {
console.log(`hello world, I want to say ${this.tag} ${something}`)
}
}
window.customElements.define('hello-world', HelloWorld);
const hw = document.querySelector('hello-world');
hw.say('good'); </script>
</body>
</html>
渲染效果為忌堂,可以看到樣式已經(jīng)互相污染:
- 使用 Shadow DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components</title>
<style> h1 {
font-size: 20px;
color: yellow;
} </style>
</head>
<body>
<div></div>
<hello-world></hello-world>
<h1>Hello World! 外部</h1>
<script type="module"> class HelloWorld extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
h1 {
font-size: 30px;
display: block;
padding: 10px;
background-color: #eee;
}
</style>
<h1>Hello World! 自定義組件內(nèi)部</h1>
`;
}
tag = 'hello-world'
say(something) {
console.log(`hello world, I want to say ${this.tag} ${something}`)
}
}
window.customElements.define('hello-world', HelloWorld);
const hw = document.querySelector('hello-world');
hw.say('good'); </script>
</body>
</html>
渲染結(jié)果為:
可以清晰的看到樣式直接互相隔離無污染,這就是Shadow DOM的好處酗洒。
HTML templates(HTML模板)
template模板可以說是大家比較熟悉的一個標(biāo)簽了士修,在Vue項目中的單頁面組件中我們經(jīng)常會用到,但是它也是 Web Components API 提供的一個標(biāo)簽樱衷,它的特性就是包裹在 template 中的 HTML 片段不會在頁面加載的時候解析渲染棋嘲,但是可以被 js 訪問到,進行一些插入顯示等操作矩桂。所以它作為自定義組件的核心內(nèi)容封字,用來承載 HTML 模板,是不可或缺的一部分耍鬓。
使用場景如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components</title>
<style> h1 {
font-size: 20px;
color: yellow;
} </style>
</head>
<body>
<div></div>
<hello-world></hello-world>
<template id="hw">
<style> .box {
padding: 20px;
}
.box > .first {
font-size: 24px;
color: red;
}
.box > .second {
font-size: 14px;
color: #000;
} </style>
<div class="box">
<p class="first">Hello</p>
<p class="second">World</p>
</div>
</template>
<script type="module"> class HelloWorld extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
root.appendChild(document.getElementById('hw').content.cloneNode(true));
}
}
window.customElements.define('hello-world', HelloWorld); </script>
</body>
</html>
渲染結(jié)果為:
Slot 大家應(yīng)該也比較熟悉了阔籽,相當(dāng)于一個連接組件內(nèi)部和外部的一個占位機制,可以用來傳遞 HTML 代碼片段牲蜀,這里我就不過多贅述笆制,有需要繼續(xù)了解的同學(xué),Google一下即可涣达。
說完了 Web Components 的“三駕馬車”在辆,大家一定對于 Web Components 有了深入的了解,也熟悉了 Web Components 一些常規(guī)寫法度苔。