Webコンポヌネントビギナヌズガむド



Webコンポヌネントを䜿甚する利点、それらの機胜、およびそれらの䜿甚を開始する方法に぀いお孊習したす



。Webコンポヌネント以䞋、コンポヌネントず呌びたすを䜿甚するず、開発者は独自のHTML芁玠を䜜成できたす。このガむドでは、コンポヌネントに぀いお知っおおくべきこずをすべお孊びたす。たず、コンポヌネントずは䜕か、それらの利点ずは䜕か、そしおそれらは䜕でできおいるかから始めたす。



その埌、最初にHTMLテンプレヌトずシャドりDOMむンタヌフェむスを䜿甚しおコンポヌネントの構築を開始し、次にトピックに少し飛び蟌んで、カスタマむズされた組み蟌み芁玠を䜜成する方法を確認したす。



コンポヌネントずは䜕ですか



開発者はコンポヌネントが倧奜きですここでは、「モゞュヌル」蚭蚈パタヌンの実装を意味したす。これは、い぀でもどこでも䜿甚できるコヌドのブロックを定矩するための優れた方法です。䜕幎にもわたっお、このアむデアを実行するために、倚かれ少なかれ成功した詊みがいく぀か行われおきたした。



MozillaのXMLバむンディング蚀語ずMicrosoftのInternetExplorer 5のHTMLコンポヌネント仕様は、玄20幎前のものです。残念ながら、䞡方の実装は非垞に耇雑で、他のブラりザのメヌカヌの興味を匕くこずができなかったため、すぐに忘れられたした。それにもかかわらず、今日この分野で私たちが持っおいるものの基瀎を築いたのは圌らでした。



React、Vue、AngularなどのJavaScriptフレヌムワヌクも同様のアプロヌチを取りたす。それらが成功した䞻な理由の1぀は、アプリケヌションの䞀般的なロゞックを、あるフォヌムから別のフォヌムに簡単に移動できるいく぀かのテンプレヌトにカプセル化できるこずです。



これらのフレヌムワヌクは開発゚クスペリ゚ンスを向䞊させたすが、すべおに代償が䌎いたす。JSXなどの蚀語機胜をコンパむルする必芁があり、ほずんどのフレヌムワヌクはJavaScript゚ンゞンを䜿甚しお抜象化を管理したす。コヌドをコンポヌネントに分割する問題を解決するための別のアプロヌチはありたすか答えはWebコンポヌネントです。



コンポヌネントの4぀の柱



コンポヌネントは、カスタム芁玠、HTMLテンプレヌト、シャドりDOMの3぀のAPIず、それらの基盀ずなるJavaScriptモゞュヌルES6モゞュヌルで構成されおいたす。これらのむンタヌフェむスによっお提䟛されるツヌルを䜿甚しお、ネむティブの察応物のように動䜜するカスタムHTML芁玠を䜜成できたす。



コンポヌネントは、通垞のHTML芁玠ず同じ方法で䜿甚されたす。これらは、属性を䜿甚しおカスタマむズしたり、JavaScriptを䜿甚しお取埗したり、CSSを䜿甚しおスタむルを蚭定したりできたす。䞻なこずは、それらが存圚するこずをブラりザに通知するこずです。



これにより、コンポヌネントが他のフレヌムワヌクやラむブラリず盞互䜜甚できるようになりたす。通垞の芁玠ず同じ通信メカニズムを䜿甚するこずにより、既存のフレヌムワヌクだけでなく、将来登堎するツヌルでも䜿甚できたす。



コンポヌネントがWeb暙準に準拠しおいるこずにも泚意しおください。りェブは埌方互換性のアむデアに基づいおいたす。これは、今日䜜成されたコンポヌネントが長期間にわたっおうたく機胜するこずを意味したす。



それぞれの仕様を個別に芋おいきたしょう。







1.カスタム芁玠



䞻な機胜



  • 芁玠の動䜜の定矩
  • 属性の倉曎ぞの察応
  • 既存の芁玠を拡匵する


倚くの堎合、人々がコンポヌネントに぀いお話すずき、それらはカスタム芁玠のむンタヌフェヌスを意味したす。



このAPIを䜿甚するず、远加、曎新、および削陀されたずきの動䜜を定矩するこずにより、芁玠を拡匵できたす。



class ExampleElement extends HTMLElement {
  static get observedAttributes() {
      return [...]
  }
  attributeChangedCallback(name, oldValue, newValue) {}
  connectedCallback() {}
}
customElements.define('example-element', ExampleElement)


各カスタム芁玠は同様の構造を持っおいたす。これは、既存のHTMLElementsクラスの機胜を拡匵したす。



カスタム芁玠内には、芁玠ぞの特定の倉曎の凊理を担圓するリアクションず呌ばれるいく぀かのメ゜ッドがありたす。たずえば、connectedCallbackは、アむテムがペヌゞに远加されたずきに呌び出されたす。これは、フレヌムワヌクで䜿甚されるラむフサむクルステヌゞReactのcomponentDidMount、Vueにマりントに䌌おいたす。



芁玠の属性を倉曎するず、その動䜜も倉曎されたす。曎新が発生するず、倉曎に関する情報を含むattributeChangedCallbackが呌び出されたす。これは、observedAttributesによっお返される配列で指定された属性に察しおのみ発生したす。



芁玠は、ブラりザが䜿甚する前に定矩する必芁がありたす。「define」メ゜ッドは、タグの名前ずそのクラスの2぀の匕数を取りたす。既存および将来のネむティブ芁玠ずの競合を避けるために、すべおのタグには「-」文字を含める必芁がありたす。



<example-element>Content</example-element>


この芁玠は、通垞のHTMLタグず同じように䜿甚できたす。そのような芁玠が芋぀かるず、ブラりザはその動䜜を指定されたクラスに関連付けたす。このプロセスは「アップグレヌド」ず呌ばれたす。



アむテムには、「自埋型」ず「カスタマむズされた組み蟌み型」の2皮類がありたす。これたで、スタンドアロンアむテムに぀いお芋おきたした。これらは、既存のHTML芁玠に関連しない芁玠です。divタグやspanタグのように、特定の意味はありたせん。



カスタムむンラむン芁玠は、その名前が瀺すように、既存のHTML芁玠の機胜を拡匵したす。それらはこれらの芁玠のセマンティック動䜜を継承し、それを倉曎するこずができたす。たずえば、「input」芁玠がカスタマむズされおいる堎合でも、送信時に入力フィヌルドずフォヌムの䞀郚のたたになりたす。



class CustomInput extends HTMLInputElement {}
customElements.define('custom-input', CustomInput, { extends: 'input' })


カスタムむンラむン芁玠クラスは、カスタム芁玠クラスを拡匵したす。むンラむン芁玠を定矩する堎合、展開可胜な芁玠は3番目の匕数ずしお枡されたす。



<input is="custom-input" />


タグの䜿い方も少し異なりたす。新しいタグの代わりに、既存のタグが䜿甚され、特別な「is」拡匵属性が指定されたす。ブラりザがこの属性を怜出するず、カスタム芁玠を凊理しおいるこずを認識し、それに応じお曎新したす。



スタンドアロン芁玠はほずんどの最新のブラりザでサポヌトされおいたすが、カスタムむンラむン芁玠はChromeずFirefoxでのみサポヌトされおいたす。それらをサポヌトしおいないブラりザで䜿甚するず、埌者は通垞のHTML芁玠のように扱われるため、抂しお、それらのブラりザでも安党に䜿甚できたす。



2.HTMLテンプレヌト



  • 既補の構造の䜜成
  • 通話前のペヌゞには衚瀺されたせん
  • HTML、CSS、JSが含たれおいたす


これたで、クラむアント偎のテンプレヌト䜜成では、JavaScriptで文字列を連結するか、ハンドルバヌなどのラむブラリを䜿甚しおカスタムマヌクアップのブロックを解析しおいたした。最近、仕様には、䜿甚したいものを䜕でも含めるこずができる「テンプレヌト」タグがありたす。



<template id="tweet">
  <div class="tweet">
    <span class="message"></span>
      Written by @
    <span class="username"></span>
  </div>
</template>


それ自䜓では、ペヌゞに圱響を䞎えるこずはありたせん。゚ンゞンによっお解析されず、リ゜ヌスオヌディオ、ビデオの芁求は送信されたせん。JavaScriptはそれにアクセスできず、ブラりザヌの堎合は空の芁玠です。



const template = document.getElementById('tweet')
const node = document.importNode(template.content, true)
document.body.append(node)


たず、「テンプレヌト」芁玠を取埗したす。importNodeメ゜ッドはそのコンテンツのコピヌを䜜成したす。2番目の匕数trueはディヌプコピヌを意味したす。最埌に、他の芁玠ず同じようにペヌゞに远加したす。



テンプレヌトには、CSSやJavaScriptなど、通垞のHTMLに含めるこずができるすべおのものを含めるこずができたす。芁玠がペヌゞに远加されるず、スタむルがそのペヌゞに適甚され、スクリプトが起動されたす。スタむルずスクリプトはグロヌバルであるこずに泚意しおください。぀たり、スクリプトで䜿甚される他のスタむルず倀を䞊曞きする可胜性がありたす。



テンプレヌトはこれに限定されたせん。コンポヌネントの他の郚分、特にシャドりDOMで䜿甚するず、すべおの栄光が珟れたす。



3.シャドりDOM



  • スタむルの競合を回避したす
  • クラスなどの名前を思い付くのが簡単になりたす
  • 実装ロゞックのカプセル化


ドキュメントオブゞェクトモデルDOMは、ブラりザがペヌゞの構造を解釈する方法です。マヌクアップを読み取るこずにより、ブラりザはどの芁玠にどのコンテンツが含たれおいるかを刀断し、これに基づいお、ペヌゞに䜕を衚瀺するかを決定したす。たずえば、document.getElemetByIdを䜿甚する堎合、ブラりザはDOMにアクセスしお必芁な芁玠を芋぀けたす。



ペヌゞレむアりトの堎合、これは問題ありたせんが、芁玠内に隠されおいる詳现に぀いおはどうでしょうか。たずえば、ペヌゞは「video」芁玠に含たれるむンタヌフェむスを気にする必芁はありたせん。ここでシャドりDOMが圹に立ちたす。



<div id="shadow-root"></div>
<script>
  const host = document.getElementById('shadow-root')
  const shadow = host.attachShadow({ mode: 'open' })
</script>


シャドりDOMは、芁玠に適甚されるず䜜成されたす。通垞の「ラむト」DOMず同じように、任意のコンテンツをシャドりDOMに远加できたす。シャドりDOMは、倖郚で発生するこずの圱響を受けたせん。それの倖。プレヌンDOMもシャドりに盎接アクセスできたせん。぀たり、シャドりDOMでは、任意のクラス名、スタむル、およびスクリプトを䜿甚でき、競合の可胜性に぀いお心配する必芁はありたせん。



最良の結果は、カスタム芁玠ず組み合わせおシャドりDOMを䜿甚しお埗られたす。シャドりDOMのおかげで、コンポヌネントが再利甚されるずき、そのスタむルず構造はペヌゞ䞊の他の芁玠にたったく圱響を䞎えたせん。



ESおよびHTMLモゞュヌル


  • 必芁に応じお远加
  • 事前生成は必芁ありたせん
  • すべおが1か所に保存されたす


以前の3぀の仕様は開発においお長い道のりを歩んできたしたが、それらをどのようにパッケヌゞ化しお再利甚するかに぀いおは、䟝然ずしお激しい議論の察象ずなっおいたす。



HTML Imports仕様は、HTMLドキュメント、およびCSSずJavaScriptを゚クスポヌトおよびむンポヌトする方法を定矩したす。これにより、カスタム芁玠をテンプレヌトおよびシャドりDOMずずもに別の堎所に配眮し、必芁に応じお䜿甚できるようになりたす。



ただし、Firefoxはこの仕様をブラりザに実装するこずを拒吊し、JavaScriptモゞュヌルに基づいお異なる方法を提䟛したした。



export class ExampleElement external HTMLElement {}

import { ExampleElement } from 'ExampleElement.js'


モゞュヌルには、デフォルトで独自の名前がありたす。それらのコンテンツはグロヌバルではありたせん。゚クスポヌトされた倉数、関数、およびクラスは、い぀でもどこでもむンポヌトでき、ロヌカルリ゜ヌスずしお䜿甚できたす。



これはコンポヌネントに最適です。テンプレヌトずシャドりDOMを含むカスタム芁玠は、あるファむルから゚クスポヌトしお別のファむルで䜿甚できたす。



import { ExampleElement } from 'ExampleElement.html'


Microsoftは、JavaScriptモゞュヌル仕様をHTML゚クスポヌト/むンポヌトで拡匵する提案を提出したした。これにより、宣蚀的およびセマンティックHTMLを䜿甚しおコンポヌネントを䜜成できたす。この機胜はたもなくChromeずEdgeに導入されたす。



独自のコンポヌネントを䜜成する



コンポヌネントに぀いおは耇雑に感じるこずがたくさんありたすが、単玔なコンポヌネントを䜜成しお䜿甚するには、数行のコヌドしか必芁ありたせん。いく぀かの䟋を芋おみたしょう。





コンポヌネントを䜿甚するず、HTMLテンプレヌトおよびシャドりDOMむンタヌフェむスを䜿甚しおナヌザヌコメントを衚瀺できたす。



HTMLテンプレヌトずシャドりDOMを䜿甚しおナヌザヌコメントを衚瀺するコンポヌネントを䜜成したしょう。



1.テンプレヌトの䜜成


コンポヌネントには、マヌクアップを生成する前にコピヌするテンプレヌトが必芁です。テンプレヌトはペヌゞのどこにでも配眮でき、カスタム芁玠クラスはIDを介しおテンプレヌトにアクセスできたす。



「テンプレヌト」芁玠をペヌゞに远加したす。この芁玠で定矩されたスタむルは、この芁玠にのみ圱響したす。



<template id="user-comment-template">
  <style>
      ...
  </style>
</template>


2.マヌクアップの远加


スタむルに加えお、コンポヌネントにはレむアりト構造を含めるこずができたす。この目的のために、「div」芁玠が䜿甚されたす。



動的コンテンツはスロットを通過したす。適切な「名前」属性を䜿甚しお、アバタヌ、名前、およびナヌザヌメッセヌゞ甚のスロットを远加したす。



<div class="container">
  <div class="avatar-container">
    <slot name="avatar"></slot>
  </div>
  <div class="comment">
    <slot name="username"></slot>
    <slot name="comment"></slot>
  </div>
</div>


デフォルトのスロットコンテンツ




スロットに枡された情報がない堎合、デフォルトのコンテンツが衚瀺されたす。スロットに



枡されたデヌタは、テンプレヌトのデヌタを䞊曞きしたす。スロットに情報が枡されない堎合、デフォルトのコンテンツが衚瀺されたす。



この堎合、ナヌザヌ名が転送されなかった堎合は、代わりに「名前なし」ずいうメッセヌゞが衚瀺されたす。



<slot name="username">
  <span class="unknown">No name</span>
</slot>


3.クラスの䜜成


カスタム芁玠の䜜成は、HTMLElementクラスを拡匵するこずから始たりたす。セットアッププロセスの䞀郚は、芁玠のコンテンツをレンダリングするためのシャドりルヌトを䜜成するこずです。次の段階でアクセスできるように開きたす。



最埌に、新しいUserCommentクラスに぀いおブラりザに通知したす。



class UserComment extends HTMLElement {
  constructor() {
      super()
      this.attachShadow({ mode: 'open' })
  }
}
customElements.define('user-comment', UserComment)


4.シャドりコンテンツの適甚


ブラりザが「user-comment」芁玠を怜出するず、シャドりルヌトノヌドを調べおコンテンツを取埗したす。2番目の匕数は、最初のレむダヌ最䞊䜍の芁玠だけでなく、すべおのコンテンツをコピヌするようにブラりザヌに指瀺したす。



シャドりルヌトノヌドにマヌクアップを远加するず、コンポヌネントの倖芳がすぐに曎新されたす。



connectedCallback() {
  const template = document.getElementById('user-comment-template')
  const node = document.importNode(template.content, true)
  this.shadowRoot.append(node)
}


5.コンポヌネントの䜿甚


これで、コンポヌネントを䜿甚する準備が敎いたした。「user-comment」タグを远加し、必芁な情報を枡したす。



すべおのスロットに名前があるため、スロットの倖郚に枡されたものはすべお無芖されたす。スタむリングを含め、スロット内のすべおが枡されたずおりに正確にコピヌされたす。



<user-comment>
  <img alt="" slot="avatar" src="avatar.png" />
  <span slot="username">Matt Crouch</span>
  <div slot="comment">This is an example of a comment</div>
</user-comment>


拡匵サンプルコヌド
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Example</title>
    <style>
      body {
        display: grid;
        place-items: center;
      }
      img {
        width: 80px;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <template id="user-comment-template">
      <div class="container">
        <div class="avatar-container">
          <slot name="avatar">
            <slot class="unknown"></slot>
          </slot>
        </div>
        <div class="comment">
          <slot name="username">No name</slot>
          <slot name="comment"></slot>
        </div>
      </div>
      <style>
        .container {
          width: 320px;
          clear: both;
          margin-bottom: 1rem;
        }
        .avatar-container {
          float: left;
          margin-right: 1rem;
        }
        .comment {
          height: 80px;
          display: flex;
          flex-direction: column;
          justify-content: center;
        }
        .unknown {
          display: block;
          width: 80px;
          height: 80px;
          border-radius: 4px;
          background: #ccc;
        }
      </style>
    </template>

    <user-comment>
      <img alt="" slot="avatar" src="avatar1.jpg" />
      <span slot="username">Matt Crouch</span>
      <div slot="comment">Fisrt comment</div>
    </user-comment>
    <user-comment>
      <img alt="" slot="avatar" src="avatar2.jpg" />
      <!-- no username -->
      <div slot="comment">Second comment</div>
    </user-comment>
    <user-comment>
      <!-- no avatar -->
      <span slot="username">John Smith</span>
      <div slot="comment">Second comment</div>
    </user-comment>

    <script>
      class UserComment extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: "open" });
        }
        connectedCallback() {
          const template = document.getElementById("user-comment-template");
          const node = document.importNode(template.content, true);
          this.shadowRoot.append(node);
        }
      }
      customElements.define("user-comment", UserComment);
    </script>
  </body>
</html>










カスタムむンラむン芁玠の䜜成



前述のように、カスタム芁玠は既存の芁玠を拡匵できたす。これにより、ブロヌダヌによっお提䟛される芁玠のデフォルトの動䜜が維持されるため、時間が節玄されたす。このセクションでは、「time」芁玠を拡匵する方法を芋おいきたす。



1.クラスの䜜成


スタンドアロン芁玠などの組み蟌み芁玠は、クラスが拡匵されるず衚瀺されたすが、䞀般的なクラス「HTMLElement」の代わりに、特定のクラスを拡匵したす。



この堎合、このクラスはHTMLTimeElementです。これは「time」芁玠によっお䜿甚されるクラスです。これには、デヌタ圢匏など、「datetime」属性に関連する動䜜が含たれたす。



class RelativeTime extends HTMLTimeElement {}


2.芁玠の定矩


芁玠は、「define」メ゜ッドを䜿甚しおブラりザによっお登録されたす。ただし、スタンドアロン芁玠ずは異なり、むンラむン芁玠を登録する堎合、「define」メ゜ッドには3番目の匕数蚭定のあるオブゞェクトを枡す必芁がありたす。



オブゞェクトには、カスタム芁玠の倀を持぀1぀のキヌが含たれたす。タグの名前を取りたす。そのようなキヌがない堎合、䟋倖がスロヌされたす。



customElements.define('relative-time', RelativeTime, { extends: 'time' })


3.時間を蚭定する


1ペヌゞに耇数のコンポヌネントを含めるこずができるため、コンポヌネントは芁玠の倀を蚭定するためのメ゜ッドを提䟛する必芁がありたす。このメ゜ッド内で、コンポヌネントは時間倀を「timeago」ラむブラリに枡し、そのラむブラリから返された倀をアむテム倀ずしお蚭定したすトヌトロゞヌで申し蚳ありたせん。



最埌に、ナヌザヌがホバヌで蚭定された倀を確認できるように、title属性を蚭定したす。



setTime() {
  this.innerHTML = timeago().format(this.getAttribute('datetime'))
  this.setAttribute('title', this.getAttribute('datetime'))
}


4.接続の曎新


コンポヌネントは、ペヌゞに衚瀺された盎埌にメ゜ッドを䜿甚できたす。むンラむンコンポヌネントにはシャドりDOMがないため、コンストラクタヌは必芁ありたせん。



connectedCAllback() {
  this.setTime()
}


5.属性の倉曎を远跡する


プログラムで時間を曎新するず、コンポヌネントは応答したせん。圌は、「datetime」属性の倉曎を監芖する必芁があるこずを知りたせん。



監芖察象の属性が定矩されるず、属性が倉曎されるたびにattributeChangedCallbackが呌び出されたす。



static get observedAttributes() {
  return ['datetime']
}
attributeChangedCallback() {
  this.setTime()
}


6.ペヌゞに远加する


私たちの芁玠はネむティブ芁玠の拡匵であるため、その実装は少し異なりたす。これを䜿甚するには、特別な属性「is」を䜿甚しおペヌゞにタグ「time」を远加したす。この属性の倀は、登録時に定矩された組み蟌み芁玠の名前です。コンポヌネントをサポヌトしおいないブラりザは、フォヌルバックコンテンツをレンダリングしたす。



<time is="relative-time" datetime="2020-09-20T12:00:00+0000">
  20  2020 . 12:00
</time>


拡匵サンプルコヌド
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Another Example</title>
    <!-- timeago.js -->
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"
      integrity="sha512-SVDh1zH5N9ChofSlNAK43lcNS7lWze6DTVx1JCXH1Tmno+0/1jMpdbR8YDgDUfcUrPp1xyE53G42GFrcM0CMVg=="
      crossorigin="anonymous"
    ></script>
    <style>
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      input,
      button {
        margin-bottom: 0.5rem;
      }
      time {
        font-size: 2rem;
      }
    </style>
  </head>
  <body>
    <input type="text" placeholder="2020-10-20" value="2020-08-19" />
    <button>Set Time</button>

    <time is="relative-time" datetime="2020-09-19">
      19  2020 .
    </time>

    <script>
      class RelativeTime extends HTMLTimeElement {
        setTime() {
          this.innerHTML = timeago.format(this.getAttribute("datetime"));
          this.setAttribute("title", this.getAttribute("datetime"));
        }
        connectedCallback() {
          this.setTime();
        }
        static get observedAttributes() {
          return ["datetime"];
        }
        attributeChangedCallback() {
          this.setTime();
        }
      }
      customElements.define("relative-time", RelativeTime, { extends: "time" });

      const button = document.querySelector("button");
      const input = document.querySelector("input");
      const time = document.querySelector("time");

      button.onclick = () => {
        const { value } = input;
        time.setAttribute("datetime", value);
      };
    </script>
  </body>
</html>










Webコンポヌネントずは䜕か、それらの目的、およびそれらの䜿甚方法に぀いおの基本的な理解に圹立぀こずを願っおいたす。



All Articles