使用純粹的JS構建 Web Component

原文連接:https://ayushgp.github.io/html-web-components-using-vanilla-jscss

譯者:阿里雲 - 也樹html

Web Component 出現有一陣子了。 Google 費了很大力氣去推進它更普遍的應用,可是除 Opera 和 Chrome 之外的多數主流瀏覽器對它的支持仍然不夠理想。html5

可是經過 polyfill,你能夠從如今開始構建你本身的 Web Component,你能夠在這裏找到相關支持:www.webcomponents.org/polyfillsgit

在這篇文章中,我會演示如何建立帶有樣式,擁有交互功能而且在各自文件中優雅組織的 HTML 標籤。github

介紹

Web Component 是一系列 web 平臺的 API,它們能夠容許你建立全新可定製、可重用而且封裝的 HTML 標籤,從而在普通網頁及 web 應用中使用。web

定製的組件基於 Web Component 標準構建,能夠在如今瀏覽器上使用,也能夠和任意與 HTML 交互的 JavaScript 庫和框架配合使用。ajax

用於支持 Web Component 的特性正逐漸加入 HTML 和 DOM 的規範,web 開發者使用封裝好樣式和定製行爲的新元素來拓展 HTML 會變得垂手可得。npm

它賦予了僅僅使用純粹的JS/HTML/CSS就能夠建立可重用組件的能力。若是 HTML 不能知足需求,咱們能夠建立一個能夠知足需求的 Web Component。json

舉個例子,你的用戶數據和一個 ID 有關,你但願有一個能夠填入用戶 ID 而且能夠獲取相應數據的組件。HTML 多是下面這個樣子:瀏覽器

<user-card user-id="1"></user-card>

複製代碼

這是一個 Web Component 最基本的應用。下面的教程將會聚焦在如何構建這個用戶卡片組件。

Web Component 的四個核心概念

HTML 和 DOM 標準定義了四種新的標準來幫助定義 Web Component。這些標準以下:

  1. 定製元素(Custom Elements): web 開發者能夠經過定製元素建立新的 HTML 標籤、加強已有的 HTML 標籤或是二次開發其它開發者已經完成的組件。這個 API 是 Web Component 的基石。

  2. HTML 模板(HTML Templates): HTML 模板定義了新的元素,描述一個基於 DOM 標準用於客戶端模板的途徑。模板容許你聲明標記片斷,它們能夠被解析爲 HTML。這些片斷在頁面開始加載時不會被用到,以後運行時會被實例化。

  3. Shadow DOM: Shadow DOM 被設計爲構建基於組件的應用的一個工具。它能夠解決 web 開發的一些常見問題,好比容許你把組件的 DOM 和做用域隔離開,而且簡化 CSS 等等。

  4. HTML 引用(HTML Imports): HTML 模板(HTML Templates)容許你建立新的模板,一樣的,HTML 引用(HTML imports)容許你從不一樣的文件中引入這些模板。經過獨立的HTML文件管理組件,能夠幫助你更好的組織代碼。

定義定製元素

咱們首先須要聲明一個類,定義元素如何表現。這個類須要繼承 HTMLElement 類,但讓咱們先繞過這部分,先來討論定製元素的生命週期方法。你可使用下面的生命週期回調函數:

  • connectedCallback — 每當元素插入 DOM 時被觸發。

  • disconnectedCallback — 每當元素從 DOM 中移除時被觸發。

  • attributeChangedCallback — 當元素上的屬性被添加、移除、更新或取代時被觸發。

UserCard 文件夾下建立 UserCard.js:

class UserCard extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('click', e => {
      this.toggleCard();
    });
  }

  toggleCard() {
    console.log("Element was clicked!");
  }
}

customElements.define('user-card', UserCard);

複製代碼

這個例子裏咱們已經建立了一個定義了定製元素行爲的類。customElements.define('user-card', UserCard); 函數調用告知 DOM 咱們已經建立了一個新的定製元素叫 user-card,它的行爲被 UserCard 類定義。如今能夠在咱們的 HTML 裏使用 user-card 元素了。

咱們會用到 https://jsonplaceholder.typicode.com/ 的 API 來建立咱們的用戶卡片。下面是數據的樣例:

{
  id: 1,
  name: "Leanne Graham",
  username: "Bret",
  email: "Sincere@april.biz",
  address: {
    street: "Kulas Light",
    suite: "Apt. 556",
    city: "Gwenborough",
    zipcode: "92998-3874",
    geo: {
      lat: "-37.3159",
      lng: "81.1496"
    }
  },
  phone: "1-770-736-8031 x56442",
  website: "hildegard.org"
}

複製代碼

建立模板

如今,讓咱們建立一個將在屏幕上渲染的模板。建立一個名爲 UserCard.html 的新文件,內容以下:

<template id="user-card-template">
  <div>
    <h2>
      <span></span> (
      <span></span>)
    </h2>
    <p>Website: <a></a></p>
    <div>
      <p></p>
    </div>
    <button class="card__details-btn">More Details</button>
  </div>
</template>
<script src="/UserCard/UserCard.js"></script>

複製代碼

注意:咱們在類名前加了一個 card__ 前綴。在較早版本的瀏覽器中,咱們不能使用 shadow DOM 來隔離組件 DOM。這樣當咱們爲組件編寫樣式時,能夠避免意外的樣式覆蓋。

編寫樣式

咱們建立好了卡片的模板,如今來用 CSS 裝飾它。建立一個 UserCard.css 文件,內容以下:

.card__user-card-container {
  text-align: center;
  display: inline-block;
  border-radius: 5px;
  border: 1px solid grey;
  font-family: Helvetica;
  margin: 3px;
  width: 30%;
}

.card__user-card-container:hover {
  box-shadow: 3px 3px 3px;
}

.card__hidden-content {
  display: none;
}

.card__details-btn {
  background-color: #dedede;
  padding: 6px;
  margin-bottom: 8px;
}

複製代碼

如今,在 UserCard.html 文件的最前面引入這個 CSS 文件:

<link rel="stylesheet" href="/UserCard/UserCard.css">

複製代碼

樣式已經就緒,接下來能夠繼續完善咱們組件的功能。

connectedCallback

如今咱們須要定義建立元素而且添加到 DOM 中會發生什麼。注意這裏 constructorconnectedCallback 方法的區別。

constructor 方法是元素被實例化時調用,而 connectedCallback 方法是每次元素插入 DOM 時被調用。connectedCallback 方法在執行初始化代碼時是頗有用的,好比獲取數據或渲染。

小貼士: 在 UserCard.js 的頂部,定義一個常量 currentDocument。它在被引入的 HTML 腳本中是必要的,容許這些腳本有途徑操做引入模板的 DOM。像下面這樣定義:

const currentDocument = document.currentScript.ownerDocument;

複製代碼

接下來定義咱們的 connectedCallback 方法:

// 元素插入 DOM 時調用
connectedCallback() {
  const shadowRoot = this.attachShadow({mode: 'open'});

  // 選取模板而且克隆它。最終將克隆後的節點添加到 shadowDOM 的根節點。
  // 當前文檔須要被定義從而獲取引入 HTML 的 DOM 權限。
  const template = currentDocument.querySelector('#user-card-template');
  const instance = template.content.cloneNode(true);
  shadowRoot.appendChild(instance);

  // 從元素中選取 user-id 屬性
  // 注意咱們要像這樣指定卡片: 
  // <user-card user-id="1"></user-card>
  const userId = this.getAttribute('user-id');

  // 根據 user ID 獲取數據,而且使用返回的數據渲染
  fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then((response) => response.text())
      .then((responseText) => {
          this.render(JSON.parse(responseText));
      })
      .catch((error) => {
          console.error(error);
      });
}

複製代碼

渲染用戶數據

咱們已經定義好了 connectedCallback 方法,而且把克隆好的模板綁定到了 shadow root 上。如今咱們須要填充模板內容,而後在 fetch 方法獲取數據後觸發 render 方法。下面來編寫 rendertoggleCard 方法。

render(userData) {
  // 使用操做 DOM 的 API 來填充卡片的不一樣區域
  // 組件的全部元素都存在於 shadow dom 中,因此咱們使用了 this.shadowRoot 這個屬性來獲取 DOM
  // DOM 只能夠在這個子樹種被查找到
  this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
  this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
  this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
  this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4> ${userData.address.suite}, <br /> ${userData.address.street},<br /> ${userData.address.city},<br /> Zipcode: ${userData.address.zipcode}`
}

toggleCard() {
  let elem = this.shadowRoot.querySelector('.card__hidden-content');
  let btn = this.shadowRoot.querySelector('.card__details-btn');
  btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
  elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
}

複製代碼

既然組件已經完成,咱們就能夠把它用在任意項目中了。爲了繼續教程,咱們須要建立一個 index.html 文件,而後寫入下面的代碼:

<html>

<head>
  <title>Web Component</title>
</head>

<body>
  <user-card user-id="1"></user-card>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.14/webcomponents-hi.js"></script>
  <link rel="import" href="./UserCard/UserCard.html">
</body>

</html>

複製代碼

由於並非全部瀏覽器都支持 Web Component,咱們須要引入 webcomponents.js 這個文件。注意咱們用到 HTML 引用語句來引入咱們的組件。

爲了運行這些代碼,你須要建立一個靜態文件服務器。若是你不清楚如何建立,你可使用像 static-server 或者 json-server 這樣的簡易靜態服務。教程裏,咱們安裝 static-server:

$ npm install -g static-server

複製代碼

接着在你的項目目錄下,使用下面的命令運行服務器:

$ static-server

複製代碼

打開你的瀏覽器並訪問localhost:3000,你就能夠看到咱們剛剛建立的組件了。

小貼士和技巧

還有不少關於 Web Component 的東西沒有在這篇短文中寫到,我想簡單的陳述一些開發 Web Component 的小貼士和技巧。

組件的命名

  • 定製元素的名稱必須包含一個短橫線。因此 <my-tabs><my-amazing-website> 是合法的名稱, 而<foo><foo_bar> 不行。

  • 在 HTML 添加新標籤時須要確保向前兼容,不能重複註冊同一個標籤。

  • 定製元素標籤不能是自閉合的,由於 HTML 只容許一部分元素能夠自閉合。須要寫成像 <app-drawer></app-drawer> 這樣的閉合標籤形式。

拓展組件

建立組件時可使用繼承的方式。舉個例子,若是想要爲兩種不一樣的用戶建立一個 UserCard,你能夠先建立一個基本的 UserCard 而後將它拓展爲兩種特定的用戶卡片。想要了解更多組件繼承的知識,能夠查看Google web developers’ article

Lifecycle Callbacks生命週期回調函數

咱們建立了當元素加入 DOM 後自動觸發的 connectedCallback 方法。咱們一樣有元素從 DOM 中移除時觸發的 disconnectedCallback 方法。 attributesChangedCallback(attribute, oldval, newval)方法會在咱們改變定製組件的屬性時被觸發。

組件元素是類的實例

既然組件元素是類的實例,就能夠在這些類中定義公用方法。這些公用方法能夠用來容許其它定製組件/腳原本和這些組件產生交互,而不是隻能改變這些組件的屬性。

定義私有方法

能夠經過多種方式定義私有方法。我傾向於使用(當即執行函數),由於它們易寫和易理解。舉個例子,若是你建立的組件有很是複雜的內部功能,你能夠像下面這樣作:

(function() {

  // 使用第一個self參數來定義私有函數
  // 當調用這些函數時,從類中傳遞參數
  function _privateFunc(self, otherArgs) { ... }

  // 如今函數只能夠在你的類的做用域中可用
  class MyComponent extends HTMLElement {
    ...

    // 定義下面這樣的函數可讓你有途徑和這個元素交互
    doSomething() {
      ...
      _privateFunc(this, args)
    }
    ...
  }

  customElements.define('my-component', MyComponent);
})()

複製代碼

凍結類

爲了防止新的屬性被添加,須要凍結你的類。這樣能夠防止類的已有屬性被移除,或者已有屬性的可枚舉、可配置或可寫屬性被改變,一樣也能夠防止原型被修改。你可使用下面的方法:

class MyComponent extends HTMLElement { ... }
const FrozenMyComponent = Object.freeze(MyComponent);
customElements.define('my-component', FrozenMyComponent);

複製代碼

注意: 凍結類會阻止你在運行時添加補丁而且會讓你的代碼難以調試。

結論

這篇關於 Web Component 的教程做用很是有限。這能夠部分歸咎於對 Web Component 的影響很大的 React。我但願這篇文章能夠提供給你足夠的信息來讓你嘗試不添加任何依賴來構建本身的定製組件。你能夠在 定製組件 API 規範(Custom components API spec) 找到更多關於 Web Component 的信息。

你能夠在這裏閱讀第二部分的教程:使用純粹的JS構建 Web Component - Part 2!

相關文章
相關標籤/搜索