摘要:生命周期和回調在這個的基礎上,標準提供了一系列控制自定義元素的方法。生命周期和自定義元素標簽的保持一致。否則,這個屬性會返回一個表示引入的文件的文檔對象,類似于。相關的東西不多,而且它現在已經是納入生效的標準文檔中了。
前端組件化這個主題相關的內容已經火了很久很久,angular 剛出來時的 Directive 到 angular2 的 components,還有 React 的 components 等等,無一不是前端組件化的一種實現和探索,但是提上議程的 Web Components 標準是個怎樣的東西,相關的一些框架或者類庫,如 React,Angular2,甚至是 x-tag,polymer 現在實現的組件化的東西和 Web Components 標準差別在哪里?我花時間努力地把現有的 W3C Web Components 文檔看了下,然后堅強地寫下這些記錄。
首先我們需要知道,Web Components 包括了四個部分:
Custom Elements
HTML Imports
HTML Templates
Shadow DOM
這四部分有機地組合在一起,才是 Web Components。
可以用自定義的標簽來引入組件是前端組件化的基礎,在頁面引用 HTML 文件和 HTML 模板是用于支撐編寫組件視圖和組件資源管理,而 Shadow DOM 則是隔離組件間代碼的沖突和影響。
下邊分別是每一部分的筆記內容。
Custom Elements 概述Custom Elements 顧名思義,是提供一種方式讓開發者可以自定義 HTML 元素,包括特定的組成,樣式和行為。支持 Web Components 標準的瀏覽器會提供一系列 API 給開發者用于創建自定義的元素,或者擴展現有元素。
這一項標準的草案還處于不穩定的狀態,時有更新,API 還會有所變化,下邊的筆記以 Cutsom Elements 2016.02.26 這個版本為準,因為在最新的 chrome 瀏覽器已經是可以工作的了,這樣可以使用 demo 來做嘗試,最后我會再簡單寫一下最新文檔和這個的區別。
registerElement首先,我們可以嘗試在 chrome 控制臺輸入 HTMLInputElement,可以看到是有這么一個東西的,這個理解為 input DOM 元素實例化時的構造函數,基礎的是 HTMLElement。
Web Components 標準提出提供這么一個接口:
document.registerElement("x-foo", { prototype: Object.create(HTMLElement.prototype, { createdCallback: { value: function() { ... } }, ... }) })
你可以使用 document.registerElement 來注冊一個標簽,標準中為了提供 namesapce 的支持,防止沖突,規定標簽類型(也可以理解為名字)需要使用 - 連接。同時,不能是以下這一些:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
第二個參數是標簽相關的配置,主要是提供一個 prototype,這個原型對象是以 HTMLElement 等的原型為基礎創建的對象。然后你便可以在 HTML 中去使用自定義的標簽。如:
是不是嗅到了 React 的味道?好吧,React 說它自己主要不是做這個事情的。
生命周期和回調在這個 API 的基礎上,Web Components 標準提供了一系列控制自定義元素的方法。我們來一一看下:
一個自定義元素會經歷以下這些生命周期:
注冊前創建
注冊自定義元素定義
在注冊后創建元素實例
元素插入到 document 中
元素從 document 中移除
元素的屬性變化時
這個是很重要的內容,開發者可以在注冊新的自定義元素時指定對應的生命周期回調來為自定義元素添加各種自定義的行為,這些生命周期回調包括了:
createdCallback
自定義元素注冊后,在實例化之后會調用,通常多用于做元素的初始化,如插入子元素,綁定事件等。
attachedCallback
元素插入到 document 時觸發。
detachedCallback
元素從 document 中移除時觸發,可能會用于做類似 destroy 之類的事情。
attributeChangedCallback
元素屬性變化時觸發,可以用于從外到內的通信。外部通過修改元素的屬性來讓內部獲取相關的數據并且執行對應的操作。
這個回調在不同情況下有對應不同的參數:
設置屬性時,參數列表是:屬性名稱,null,值,命名空間
修改屬性時,參數列表是:屬性名稱,舊值,新值,命名空間
刪除屬性時,參數列表是:屬性名稱,舊值,null,命名空間
好了,就上邊了解到的基礎上,假設我們要創建一個自定義的 button-hello 按鈕,點擊時會 alert("hello world"),代碼如下:
document.registerElement("button-hello", { prototype: Object.create(HTMLButtonElement.prototype, { createdCallback: { value: function createdCallback() { this.innerHTML = "" this.addEventListener("click", () => { alert("hello world") }) } } }) })
擴展原有元素要留意上述代碼執行之后才能使用
其實,如果我們需要一個按鈕,完全不需要重新自定義一個元素,Web Components 標準提供了一種擴展現有標簽的方式,把上邊的代碼調整一下:
document.registerElement("button-hello", { prototype: Object.create(HTMLButtonElement.prototype, { createdCallback: { value: function createdCallback() { this.addEventListener("click", () => { alert("hello world") }) } } }), extends: "button" })
然后在 HTML 中要這么使用:
使用 is 屬性來聲明一個擴展的類型,看起來也蠻酷的。生命周期和自定義元素標簽的保持一致。
當我們需要多個標簽組合成新的元素時,我們可以使用自定義的元素標簽,但是如果只是需要在原有的 HTML 標簽上進行擴展的話,使用 is 的這種元素擴展的方式就好。
原有的 createElement 和 createElementNS,在 Web Components 標準中也擴展成為支持元素擴展,例如要創建一個 button-hello:
const hello = document.createElement("button", "button-hello")
標準文檔中還有很多細節上的內容,例如接口的參數說明和要求,回調隊列的實現要求等,這些更多是對于實現這個標準的瀏覽器開發者的要求,這里不做詳細描述了,內容很多,有興趣的自行查閱:Cutsom Elements 2016.02.26。
和最新版的區別前邊我提到說文檔的更新變化很快,截止至我寫這個文章的時候,最新的文檔是這個:Custom Elements 2016.07.21。
細節不做描述了,講講我看到的最大變化,就是向 ES6 靠攏。大致有下邊三點:
從原本的擴展 prototype 來定義元素調整為建議使用 class extends 的方式
注冊自定義元素接口調整,更加方便使用,傳入 type 和 class 即可
生命周期回調調整,createdCallback 直接用 class 的 constructor
前兩個點,我們直接看下代碼,原本的代碼按照新的標準,應該調整為:
class ButtonHelloElement extends HTMLButtonElement { constructor() { super() this.addEventListener("click", () => { alert("hello world") }) } } customElements.define("button-hello", ButtonHelloElement, { extends: "button" })
從代碼上看會感覺更加 OO,編寫上也比原本要顯得方便一些,原本的生命周期回調是調整為新的:
constructor in class 作用相當于原本的 createdCallback
connectedCallback 作用相當于 attachedCallback
disconnectedCallback 作用相當于 detachedCallback
adoptedCallback 使用 document.adoptNode(node) 時觸發
attributeChangedCallback 和原本保持一致
connect 事件和插入元素到 document 有些許區別,主要就是插入元素到 document 時,元素狀態會變成 connected,這時會觸發 connectedCallback,disconnect 亦是如此。
HTML Imports 概述HTML Imports 是一種在 HTMLs 中引用以及復用其他的 HTML 文檔的方式。這個 Import 很漂亮,可以簡單理解為我們常見的模板中的 include 之類的作用。
我們最常見的引入一個 css 文件的方式是:
Web Components 現在提供多了一個這個:
HTMLLinkElement原本的 link 標簽在添加了 HTML Import 之后,多了一個只讀的 import 屬性,當出現下邊兩種情況時,這個屬性為 null:
該 link 不是用來 import 一個 HTML 的。
該 link 元素不在 document 中。
否則,這個屬性會返回一個表示引入的 HTML 文件的文檔對象,類似于 document。比如說,在上邊的代碼基礎上,可以這樣做:
const link = document.querySelector("link[rel=import]") const header = link.import; const pulse = header.querySelector("div.logo");阻塞式
我們要知道的是,默認的 link 加載是阻塞式的,除非你給他添加一個 async 標識。
document阻塞式從某種程度上講是有必要的,當你 improt 的是一個完整的自定義組件并且需要在主 HTML 中用標簽直接使用時,非阻塞的就會出現錯誤了,因為標簽還沒有被注冊。
有一點值得留意的是,在 import 的 HTML 中,我們編寫的 script 里邊的 document 是指向 import 這個 HTML 的主 HTML 的 document。
如果我們要獲取 import 的 HTML 的 document 的話,得這么來:
const d = document.currentScript.ownerDocument
這樣設計是因為 import 進來的 HTML 需要用到主 HTML 的 document。例如我們上邊提到的 registerElement。
在一個被 import 的 HTML 文件中使用下邊三個方法會拋出一個 InvalidStateError 異常:
document.open()
document.write()
document.close()
對于 HTML Import,標準文檔中還有很大一部分內容是關于多個依賴加載的處理算法的,在這里就不詳述了,有機會的話找時間再開篇談,這些內容是需要瀏覽器去實現的。
HTML Templates 概述這個東西很簡單,用過 handlebars 的人都知道有這么一個東西:
其他模板引擎也有類似的東西,那么 HTML Templates 便是把這個東西官方標準化,提供了一個 template 標簽來存放以后需要但是暫時不渲染的 HTML 代碼。
以后可以這么寫了:
...接口和應用
template 元素有一個只讀的屬性 content,用于返回這個 template 里邊的內容,返回的結果是一個 DocumentFragment。
具體是如何使用的,直接參考官方給出的例子:
Homework Smile!
使用 DocumentFragment 的 clone 方法以 template 里的代碼為基礎創建一個元素節點,然后你便可以操作這個元素節點,最后在需要的時候插入到 document 中特定位置便可以了。
Template 相關的東西不多,而且它現在已經是納入生效的 標準文檔 中了。
我們接下來看看重磅的 Shadow DOM。
Shadow DOM 概述Shadow DOM 好像提出好久了,最本質的需求是需要一個隔離組件代碼作用域的東西,例如我組件代碼的 CSS 不能影響其他組件之類的,而 iframe 又太重并且可能有各種奇怪問題。
可以這么說,Shadow DOM 旨在提供一種更好地組織頁面元素的方式,來為日趨復雜的頁面應用提供強大支持,避免代碼間的相互影響。
看下在 chrome 它會是咋樣的:
我們可以通過 createShadowRoot() 來給一個元素節點創建 Shadow Root,這些元素類型必須是下邊列表的其中一個,否則會拋出 NotSupportedError 異常。
自定義的元素
article
aside
blockquote
body
div
header, footer
h1, h2, h3, h4, h5, h6
nav
p
section
span
createShadowRoot() 是現在 chrome 實現的 API,來自文檔:https://www.w3.org/TR/2014/WD...。最新的文檔 API 已經調整為 attachShadow()。
返回的 Shadow Root 對象從 DocumentFragment 繼承而來,所以可以使用相關的一些方法,例如 shadowRoot.getElementById("id") 來獲取 Shadow DOM 里邊的元素。
簡單的使用如下:
const div = document.getElementById("id") const shadowRoot = div.createShadowRoot() const span = document.createElement("span") span.textContent = "hello world" shadowRoot.appendChild(span)
在這里,我把這個 div 成為是這個 Shadow DOM 的 宿主元素,下邊的內容會延續使用這個稱呼。
Shadow DOM 本身就為了代碼隔離而生,所以在 document 上使用 query 時,是沒法獲取到 Shadow DOM 里邊的元素的,需要在 Shadow Root 上做 query 才行。
在這里附上一個文檔,里邊有詳細的關于新的標準和現在 blink 引擎實現的 Shadow DOM 的區別,官方上稱之為 v0 和 v1:Shadow DOM v1 in Blink。
APIShadow Root 除了從 DocumentFragment 繼承而來的屬性和方法外,還多了另外兩個屬性:
host 只讀屬性,用來獲取這個 Shadow Root 所屬的元素
innerHTML 用來獲取或者設置里邊的 HTML 字符串,和我們常用的 element.innerHTML 是一樣的
另外,在最新的標準文檔中,元素除了上邊提到的 attachShadow 方法之外,還多了三個屬性:
assignedSlot 只讀,這個元素如果被分配到了某個 Shadow DOM 里邊的 slot,那么會返回這個對應的 slot 元素
slot 元素的 slot 屬性,用來指定 slot 的名稱
shadowRoot 只讀,元素下面對應的 Shadow Root 對象
slot 是什么?接著看下邊的內容,看完下一節的最后一部分就會明白上述內容和 slot 相關的兩個 API 有什么作用。
slotslot 提供了在使用自定義標簽的時候可以傳遞子模板給到內部使用的能力,可以簡單看下 Vue 的一個例子。
我們先來看下現在 chrome 可以跑的 v0 版本,這一個版本是提供了一個 content 標簽,代表了一個占位符,并且有一個 select 屬性用來指定使用哪些子元素。
自定義的元素里邊的子元素代碼是這樣的:
hello test
那么展現的結果會和下邊的代碼是一樣的:
test
這里只是說展現結果,實際上,input-toggle 里邊應該是創建了一個 Shadow DOM,然后 content 標簽引用了目標的 span 內容,在 chrome 看是這樣的:
然后,是最新標準中的 slot 使用方式,直接上例子代碼:
在自定義的元素標簽是這么使用 slot 的:
test
通過 slot="text" 的屬性來讓元素內部的 slot 占位符可以引用到這個元素,多個元素使用這個屬性也是可以的。這樣子我們便擁有了使用標簽是從外部傳 template 給到自定義元素的內部去使用,而且具備指定放在那里的能力。
CSS 相關因為有 Shadow DOM 的存在,所以在 CSS 上又添加了很多相關的東西,其中一部分還是屬于討論中的草案,命名之類的可能會有變更,下邊提及的內容主要來自文檔:Shadow DOM in CSS scoping 1,很多部分在 chrome 是已經實現的了,有興趣可以寫 demo 試試。
因為 Shadow DOM 很大程度上是為了隔離樣式作用域而誕生的,主文檔中的樣式規則不對 Shadow DOM 里的子文檔生效,子文檔中的樣式規則也不影響外部文檔。
但不可避免的,在某些場景下,我們需要外部可以控制 Shadow DOM 中樣式,如提供一個組件給你,有時候你會希望可以自定義它內部的一些樣式,同時,Shadow DOM 中的代碼有時候可能需要能夠控制其所屬元素的樣式,甚至,組件內部可以定義上邊提到的通過 slot 傳遞進來的 HTML 的樣式。所以呢,是的,CSS 選擇器中添加了幾個偽類,我們一一來看下它們有什么作用。
在閱讀下邊描述的時候,請留意一下選擇器的代碼是在什么位置的,Shadow DOM 內部還是外部。
:host 用于在 Shadow DOM 內部選擇到其宿主元素,當它不是在 Shadow DOM 中使用時,便匹配不到任意元素。
在 Shadow DOM 中的 * 選擇器是無法選擇到其宿主元素的。
:host(
文檔中提供了一個例子:
<"shadow tree"> ...>
在這個 shadow tree 內部的樣式代碼中,會有這樣的結果:
:host 匹配
x-foo 匹配不到元素
.foo 只匹配到 .foo:host 匹配不到元素 :host(.foo) 匹配 :host-context( ::shadow 這個偽類用于在 Shadow DOM 外部匹配其內部的元素,而 /deep/ 這個標識也有同樣的作用,我們來看一個例子: 對于上述這一段代碼的 HTML 結構,在 Shadow DOM 外部的樣式代碼中,會是這樣的: x-foo::shadow > span 可以匹配到 #top 元素 #top 匹配不到元素 x-foo /deep/ span 可以匹配到 #not-top 和 #top 元素 /deep/ 這個標識的作用和我們的 > 選擇器有點類似,只不過它是匹配其對應的 Shadow DOM 內部的,這個標識可能還會變化,例如改成 >> 或者 >>> 之類的,個人感覺, >> 會更舒服。 最后一個,用于在 Shadow DOM 內部調整 slot 的樣式,在我查閱的這個文檔中,暫時是以 chrome 實現的為準,使用 ::content 偽類,不排除有更新為 ::slot 的可能性。我們看一個例子來了解一下,就算名稱調整了也是差不多的用法: 在 Shadow DOM 內部的樣式代碼中,::content div 可以匹配到 #one,#three 和 #four,留意一下 #two 為什么沒被匹配到,因為它沒有被 content 元素選中,即不會進行引用。如果更換成 slot 的 name 引用的方式亦是同理。 層疊規則,按照這個文檔的說法,對于兩個優先級別一樣的 CSS 聲明,沒有帶 !important 的,在 Shadow DOM 外部聲明的優先級高于在 Shadow DOM 內部的,而帶有 !important 的,則相反。個人認為,這是提供給外部一定的控制能力,同時讓內部可以限制一定的影響范圍。 繼承方面相對簡單,在 Shadow DOM 內部的頂級元素樣式從宿主元素繼承而來。 至此,Web Components 四個部分介紹結束了,其中有一些細節,瀏覽器實現細節,還有使用上的部分細節,是沒有提及的,因為詳細記錄的話,還會有很多東西,內容很多。當使用過程中有疑問時可以再次查閱標準文檔,有機會的話會再完善這個文章。下一部分會把這四個內容組合起來,整體看下 Web Components 是怎么使用的。 Web Components 總的來說是提供一整套完善的封裝機制來把 Web 組件化這個東西標準化,每個框架實現的組件都統一標準地進行輸入輸出,這樣可以更好推動組件的復用。結合上邊各個部分的內容,我們整合一起來看下應該怎么使用這個標準來實現我們的組件: 這是一個簡單的組件的例子,用于定義一個 test-header,并且給傳遞進來的子元素 li 添加了一些組件內部的樣式,同時給組件綁定了一個點擊事件,來打印點擊目標的文本內容。 看下如何在一個 HTML 文件中引入并且使用一個組件: 一個 import 的 把組件的 HTML 文件引用進來,這樣會執行組件中的腳本,來注冊一個 test-header 元素,這樣子我們便可以在主文檔中使用這個元素的標簽。 上邊的例子是可以在 chrome 正常運行的。 所以,根據上邊簡單的例子可以看出,各個部分的內容是有機結合在一起,Custom Elements 提供了自定義元素和標簽的能力,template 提供組件模板,import 提供了在 HTML 中合理引入組件的方式,而 Shadow DOM 則處理組件間代碼隔離的問題。 不得不承認,Web Components 標準的提出解決了一些問題,必須交由瀏覽器去處理的是 Shadow DOM,在沒有 Shadow DOM 的瀏覽器上實現代碼隔離的方式多多少少有缺陷。個人我覺得組件化的各個 API 不夠簡潔易用,依舊有 getElementById 這些的味道,但是交由各個類庫去簡化也可以接受,而 import 功能上沒問題,但是加載多個組件時性能問題還是值得商榷,標準可能需要在這個方面提供更多給瀏覽器的指引,例如是否有可能提供一種單一請求加載多個組件 HTML 的方式等。 在現在的移動化趨勢中,Web Components 不僅僅是 Web 端的問題,越來越多的開發者期望以 Web 的方式去實現移動應用,而多端復用的實現漸漸是以組件的形式鋪開,例如 React Native 和 Weex。所以 Web Components 的標準可能會影響到多端開發 Web 化的一個模式和發展。 最后,再啰嗦一句,Web Components 個人覺得還是未來發展趨勢,所以才有了這個文章。 文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。 轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80214.html
摘要:生命周期和回調在這個的基礎上,標準提供了一系列控制自定義元素的方法。生命周期和自定義元素標簽的保持一致。否則,這個屬性會返回一個表示引入的文件的文檔對象,類似于。相關的東西不多,而且它現在已經是納入生效的標準文檔中了。 前端組件化這個主題相關的內容已經火了很久很久,angular 剛出來時的 Directive 到 angular2 的 components,還有 React 的 co...
摘要:生命周期和回調在這個的基礎上,標準提供了一系列控制自定義元素的方法。生命周期和自定義元素標簽的保持一致。否則,這個屬性會返回一個表示引入的文件的文檔對象,類似于。相關的東西不多,而且它現在已經是納入生效的標準文檔中了。 前端組件化這個主題相關的內容已經火了很久很久,angular 剛出來時的 Directive 到 angular2 的 components,還有 React 的 co...
摘要:本質上,本文主要解釋內部是如何定義組件和指令的,并引入新的視圖節點定義指令定義。大多數指令使用屬性選擇器,但是有一些也選擇元素選擇器。實際上,表單指令就是使用元素選擇器來把特定行為附著在元素上。但是由于編譯器會為每一個 原文鏈接:Here is why you will not find components inside Angular showImg(https://segmen...
摘要:現在來做一個的入口讓我們在文件里進行的配置。如果想要顯示它們,我們可以在運行的時候使用你還可以使用,在改變代碼的時候自動進行打包。新建文件,里面是一段,告訴使用進行預處理。 本文譯自:Webpack your bags這篇文章由入門到深入的介紹了webpack的功能和使用技巧,真心值得一看。 由于我英語水平有限,而且很少翻譯文章,所以文中的一些語句在翻譯時做了類似語義的轉換,望諒解。...
閱讀 3170·2021-09-10 10:51
閱讀 3351·2021-08-31 09:38
閱讀 1639·2019-08-30 15:54
閱讀 3129·2019-08-29 17:22
閱讀 3214·2019-08-26 13:53
閱讀 1960·2019-08-26 11:59
閱讀 3283·2019-08-26 11:37
閱讀 3308·2019-08-26 10:47