未來的元件化標準 —— 淺嘗Web Components

甜蝦發表於2019-03-04

原文地址

前言

Web Components涉及到的內容還是很多的,每一塊都有很多東西可以講,國外的好多大佬已經產出了好多優秀的文章。 本文照常只是簡單瞭解大致內容而不進入深究,瞭解且會用即可,淺嘗輒止。

簡介

Web Components本身不是一個規範,而是由W3C提出的另外4個規範的合集。這四個規範是:

下面我們走馬觀花,簡單瞭解一個這四個東西。

HTML Template

之前的頁面開發經常的一個做法是把模板放在一個script標籤或者隱藏的div中,用的時候通過innerHTML取出,塞進資料, 然後放回頁面顯示。現在我們可以通過<template>標籤存放了。就像這樣:

<template id="mytemplate">
	<img src="" alt="great image">
  	<div class="comment"></div>
</template>
複製程式碼

特性檢測

要特性檢測 <template>,可以建立一個 template 元素並檢查它是否擁有 content 屬性:

function supportsTemplate() {
	return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
  	// 檢測通過!
} else {
  	// 使用舊的模板技術或庫。
}
複製程式碼

啟用模板

啟用模板,即渲染出模板裡面的內容。啟用模板最簡單的方法就是使用 document.importNode() 對模板的 .content 進行深拷貝。 .content 為只讀屬性,關聯一個包含模板內容的 DocumentFragment。

var t = document.querySelector('#mytemplate');
// 在執行時填充 src。
t.content.querySelector('img').src = 'logo.png';

var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
複製程式碼

特點

用 <template> 來包裹內容為我們提供了幾個重要屬性:

  • 它的內容在啟用之前一直處於惰性狀態。本質上,這些標記就是隱藏的 DOM,它們不會被渲染。

  • 處於模板中的內容不會有副作用。指令碼不會執行,圖片不會載入,音訊不會播放,...直到模板被使用。

  • 內容不在文件中。在主頁面使用 document.getElementById() 或 querySelector() 不會返回模板的子節點。

  • 模板能夠被放置在任何位置,包括 <head>,<body>,或 <frameset>,並且任何能夠出現在以上元素中的內容都可以放到模板中。 注意,"任何位置"意味著 <template> 能夠安全的出現在 HTML 解析器不允許出現的位置... 幾乎可以作為任何內容模型的子節點, 它也可以作為 <table> 或 <select> 的子元素。

推薦閱讀

HTML Imports

之前在頁面引入另一個頁面或片段往往是通過iframe或者ajax非同步載入,而現在我們可以這樣做:

在head中引入

<head>
	<link rel="import" href="/path/to/imports/stuff.html">
</head>
複製程式碼

js中獲取

var content = document.querySelector('link[rel="import"]').import;
複製程式碼

特性檢測

要檢測瀏覽器是否支援匯入,可驗證 <link> 元素上是否存在 import:

function supportsImports() {
	return 'import' in document.createElement('link');
}

if (supportsImports()) {
  	// 支援匯入
} else {
  	// 使用其他方法載入檔案
}
複製程式碼

推薦閱讀

Shadow DOM

首先需要設定一下:開啟開發者工具,f1開啟設定(或右上角三個點),然後勾上Show user agent shadow DOM ——

未來的元件化標準 —— 淺嘗Web Components

然後再看下,video標籤是這樣的 ——

未來的元件化標準 —— 淺嘗Web Components

甚至一個普通的input ——

未來的元件化標準 —— 淺嘗Web Components

之前被隱藏掉的DOM部分就是shadow DOM。顧名思義,它是其宿主元素的影子,通常用來封裝元件的內部結構。

所以像video、audio甚至input都是用簡單的元素封裝的元件。

這讓我想到,我們是不是可以通過修改元素裡面的shadow DOM的樣式來改變該元素的樣式呢? 答案是 —— 是的,但也不完全是...

未來的元件化標準 —— 淺嘗Web Components

從上圖audio標籤的結構和瀏覽器預設樣式可以看到,我們可以像這樣修改對應的樣式:

audio::-webkit-media-controls {
	...
}
複製程式碼

就像通過::-webkit-scrollbar改造瀏覽的滾動條樣式那樣,

於是,預設的audio樣式(新版chrome)——

small

經過改造後,可以變成這樣——

small

然而並不是所有樣式都可以這樣覆蓋改造,像pseudo="-internal-media-controls-loading-panel"這樣以"-internal-"開頭的是不可以的。 所以這個做法還是有很大侷限性的。

這是我試出來的,並沒發現相關標準或依據...?

demo地址

這種做法自認為只適合拿來玩玩而已,不適合投入到專案開發中去。一來是因為其侷限性太大,二來誰知道啥時候瀏覽器升級,這些標籤的內部結構就又變化了呢, 最重要的是shadow DOM是為web Components而生的,與Custom Elements一起是web Components的重要組成部分,並非用於此“旁門左道”?。

推薦閱讀

Custom Elements

自定義元素,首先有個硬性規定,自定義元素的命名中必須要有中劃線“-”,否則便是未知元素了。

自定義元素分為兩種 ——

自特性主自定義元素(Autonomous custom elements)

不具備任何已有元素的,其樣式和行為完全自定義,如我們要定義一個這樣的元素:

<flag-icon country="cn"></flag-icon>
複製程式碼

通過給屬性country賦值來顯示對應的國旗。

js的基本結構是這樣的

class FlagIcon extends HTMLElement {
  	constructor() {
    	super();
    	this._countryCode = null;
  	}

  	static get observedAttributes() { return ["country"]; }

  	attributeChangedCallback(name, oldValue, newValue) {
	    // name will always be "country" due to observedAttributes
	    this._countryCode = newValue;
	    this._updateRendering();
  	}

  	connectedCallback() {
    	this._updateRendering();
  	}

  	get country() {
    	return this._countryCode;
  	}

  	set country(v) {
    	this.setAttribute("country", v);
  	}

  	_updateRendering() {
	    //...
  	}
}

//全域性註冊該元素
customElements.define("flag-icon", FlagIcon);
複製程式碼

註冊後,也通過js建立該元素

const flagIcon = document.createElement("flag-icon");
flagIcon.country = "cn";
document.body.appendChild(flagIcon);
複製程式碼

自定義內建元素(Customized built-in elements)

繼承自已有元素,擁有已有元素的所有特性。

比如我們自定義一個按鈕,整合普通按鈕所有的特性,但是當點選的時候會有一個動效,就可以這麼做 ——

class PlasticButton extends HTMLButtonElement {
  	constructor() {
    	super();

    	this.addEventListener("click", () => {
      		// 動效邏輯
    	});
  	}
}
複製程式碼

不同的是,註冊時要加上一個引數

customElements.define("plastic-button", PlasticButton, { extends: "button" });
複製程式碼

使用時也稍有不同

<button is="plastic-button">點我!</button>
複製程式碼

通過js定義元素,則是這樣

const plasticButton = document.createElement("button", { is: "plastic-button" });
plasticButton.textContent = "點我!";
document.body.appendChild(flagIcon);
複製程式碼

生命週期

用過Vue、React等框架的同學對生命週期應該不陌生。同樣,自定義元素有4個生命週期:

connectedCallback

元素首次被插入文件DOM時觸發

disconnectedCallback

元素從文件DOM中刪除時觸發

adoptedCallback

元素被移動到新的文件時觸發

attributeChangedCallback

元素增加、刪除、修改自身屬性時觸發

推薦閱讀

來一個demo

評分元件相信大家都司空見慣了。照葫蘆畫瓢,我用原生js寫了一個Web Components 版的,簡單實現了該元件的基本功能。

demo截圖:

未來的元件化標準 —— 淺嘗Web Components

demo地址

推薦閱讀

總結

Web Components 為前端元件化提供瞭解決方案,但用慣了Vue這樣的框架,還是會發現Web Components 的問題, 比如

  • 瀏覽器的支援
  • 對樣式區域性作用域的處理,往往js中包著一堆的CSS樣式,略顯臃腫
  • 父子、兄弟元件的通訊問題
  • 屬性都是字串,需要額外的程式碼做轉換和相容
  • 沒有資料驅動,基本全是DOM操作

個人愚見,望大佬指點!?

相關文章