WebComponent魔法堂:Custom Elementの標準構築を深く追究する.


前言
 『WebComponent魔法堂:Custom Elementの痛点向けプログラミングを深く追究する』を通じて、Custom Elementは別に新しいものではないことが分かりました.自分のalert元素をIE 5.5に定義することもできます.しかし、このような簡単で粗暴なカスタム要素は必要ではありません.以下の特徴を持つカスタム要素が必要です.
  • カスタム要素は、既存の方法によって実装され得る(new CustomElement()document.createElement('CUSTOM-ELEMENT'))
  • .
  • は、カスタム要素・インスタンス(document.body.appendChildなど、CSSスタイルによって修飾されることができるなど)を従来の方法で操作することができる
  • .
  • は元素のライフサイクルを監督することができます.GoogleをはじめとするH 5 Custom Elementは、既存の標準要素に基づいて、上向のブラウザに様々な抽象的なレベルの高いカスタム要素を注入し、元素CRUD操作で生APIとシームレスにドッキングし、プログラミング体験がよりスムーズになります.次に、H 5 Custom Elementを通じて、alert要素を再定義しましょう.
  • この「小さなこと」に命名します.
     正式なロールコードの前に、皆さんに一番頭が痛いことは元素の命名方法です.以下の3つの要素は私達の名前に影響します.
  • 名前の衝突.カスタムコンポーネントは様々な第三者のクラスのように命名衝突の問題があるので、名前空間を導入して解決したいと自然に思いますが、コンポーネントの名前はコンポーネントのリソースローディングの問題に関わっていないので、ここで簡略化します.
  • 意味化.意味化とは、要素名が望文生義の境界に達するということであり、例えばcom-cnblogs-fsjohnhuang-alertは、プレフィクスであることを知っているように見える.要素の機能に関係なく、x-alertは元素の機能である.
  • 十分な吊り下げ:高い名前はいつも人を喜ばせてくれます.私達のプロジェクトチームの前に、警告システムを「超無敵グローバルポジショニング来料品質」と改名すると冗談を言っています.上記3点以外にも、H 5仕様にはこの規定があります.カスタム要素は少なくとも1つのハイフンを含んでいなければなりません.つまり、最も簡単な形式もこのようにしてください.ハイフンを付けない名前はブラウザの元の要素として残して使います.言い換えれば、名前のハイフネーションの要素は有効なカスタム要素として認識され、ハイフネーションのない要素は元の要素として認識されるか、無効な要素として認識されるということです.
  • const compose = (...fns) => {
      const lastFn = fns.pop()
      fns = fns.reverse()
      return a => fns.reduce((p, fn) => fn(p), lastFn(a))
    }
    const info = msg => console.log(msg)
    const type = o => Object.prototype.toString.call(o)
    const printType = compose(info, type)
    
    const newElem = tag => document.createElement(tag)
    
    //           
    const xAlert = newElem('x-alert')
    infoType(xAlert) // [object HTMLElement]
    
    //           
    const alert = newElem('alert')
    infoType(alert) // [object HTMLUnknownElement]
    
    //          
    const div = newElem('div')
    infoType(div) // [object HTMLDivElement]
     じゃ、私はあえてxを使って定義要素から来ますか?ブラウザで「悟空、またやんちゃ」と言ってもいいです.
    名前付き仕様により、カスタム要素とオリジナル要素を効果的に区別し、プレフィクスによって命名衝突問題を解決しました.ちょっと待ってください.プレフィックスを追加するのは本当にネーミングの衝突を解決するいい方法ですか?これは、プレフィクスを追加することによってIDの衝突を解決するのと同じです.命名衝突が発生した場合、プレフィクスをもう衝突しないまで延長します.JAVAのalertという名前が出てくる可能性があります.騒音は明らかに多く、直接的に意味化の程を下げることができます.タイピングの疲れは見ても疲れます.このすべての根源は、Schope-Global Scopeだけであり、したがって、名前の衝突を解決するための付加的な情報はコンテキストを通じて暗黙的に提供できなくなり、直接にプレフィックスによってハードに追加される必要があります. プレフィックスの方式は認めましたが、字を書かないようにしてもいいですか?名前空間のように名前が衝突した時があります.
    #!usr/bin/env python
    # -*- coding: utf-8 -*-
    from django.http import HttpResponse
    
    def index(request):
      return HttpResponse('Hello World!')
    名前の衝突がある場合
    #!usr/bin/env python
    # -*- coding: utf-8 -*-
    import django.db.models
    import peewee
    
    type(django.db.models.CharField)
    type(peewee.CharField)
    プレフィックスも選択の省略があればいいです.
    Custoome Element v 0を操作します.
     は元素に対して命名して騒がしいです.APIをする時です.
    最初から足に新しい要素を定義します.
    /** x-alert     **/
    const xAlertProto = Object.create(HTMLElement.prototype, {
      /*           */
      //       
      createdCallback: {
        value: function(){
          console.log('invoked createCallback!')
    
          const raw = this.innerHTML
          this.innerHTML = `
    ${raw}
    ` this.querySelector('button.close').addEventListener('click', _ => this.close()) } }, // DOM attachedCallback: { value: function(){ console.log('invoked attachedCallback!') } }, // DOM detachedCallback: { value: function(){ console.log('invoked detachedCallback!') } }, // attribute attributeChangedCallback: { value: function(attrName, oldVal, newVal){ console.log(`attributeChangedCallback-change ${attrName} from ${oldVal} to ${newVal}`) } }, /* */ // textContent textContent: { get: function(){ return this.querySelector('.content').textContent }, set: function(val){ this.querySelector('.content').textContent = val } }, close: { value: function(){ this.style.display = 'none' } }, show: { value: function(){ this.style.display = 'block' } } }) // const XAlert = document.registerElement('x-alert', { prototype: xAlertProto }) /** **/ // const xAlert1 = new XAlert() // invoked createCallback! const xAlert2 = document.createElement('x-alert') // invoked createCallback! // DOM document.body.appendChild(xAlert1) // invoked attachedCallback! // DOM xAlert1.remove() // invoked detachedCallback! // DIV , DOM attachedCallback detachedCallback const d = document.createElement('div') d.appendChild(xAlert1) xAlert1.remove() // xAlert1.textContent = 1 console.log(xAlert1.textContent) // 1 xAlert1.close() // xAlert1.setAttribute('d', 1) // attributeChangedCallback-change d from null to 1 xAlert1.removeAttribute('d') // attributeChangedCallback-change d from 1 to null // setAttributeNode removeAttributeNode attributeChangedCallback
    上記のa-b要素を定義することにより、Custom ElementのすべてのAPIが示されているが、alertインターフェースを継承した後、4つのライフサイクルのコールバック方法を選択的に実現し、com-cnblogs-fsjohnhuang-alertにカスタム要素コンテンツ展開の論理が書かれている.また、要素の公開属性と方法を定義することができる.最後にx-alert方法を通じて、ブラウザに新しい要素を定義したことを知らせます. 今の問題はHTMLElementこのHTML MarkpがcreatedCallbackコールの前に現れたら、どんな状況がありますか?このときのdocument.registerElement要素は、unresoloved状態にあり、CSS Selector によって捕捉されてもよく、document.registerElementを実行すると、x-alert要素は、resoved状態にある.このように、2つの状態に対してスタイル調整ができます.アンレスレベルの状態を知らせる要素はまだ使えません.ご期待ください.
    漸進的にオリジナル要素を強化する
    時には既存の元素の基礎の上で機能を強化したいだけです.もしまた初めからやり始めるなら、それは大変です.幸いCustom Elementの規範はもう私達のために考えられました.次にinput元素を強化します.
    const xInputProto = Object.create(HTMLInputElement.prototype, {
      createdCallback: {
        value: function(){ this.value = 'x-input' }
      },
      isEmail: {
        value: function(){
          const val = this.value
          return /[0-9a-zA-Z]+@\S+\.\S+/.test(val)
        }
      }
    })
    document.registerElement('x-input', {
      prototype: xInputProto,
      extends: 'input'
    })
    
    //   
    const xInput1 = document.createElement('input', 'x-input') // 
    console.log(xInput1.value) // x-input
    console.log(xInput1.isEmail()) // false
    Custom Element v 1——着替えるだけです.
    Custom Element APIは現在v 1バージョンにアップグレードされていますが、実際には専用の:unresolvedを入口として統一的に管理し、ユーザー定義要素を操作し、ES 6 classに対してより友好的な方法で要素を定義しています.その中のステップと概念はあまり変化していません.以下はCustom Element v 1のAPIを使って上記の二つの例を書き直します.
  • は、最初から
  • を定義する.
    class XAlert extends HTMLElement{
      //    v0  createdCallback,      v0  createdCallback     resolved      , v1  constructor        undefined      ,          connectedCallback   
      constructor(){
        super() //            
    
        const raw = this.innerHTML
        this.innerHTML = `
    ${raw}
    ` this.querySelector('button.close').addEventListener('click', _ => this.close()) } // v0 attachedCallback connectedCallback(){ console.log('invoked connectedCallback!') } // v0 detachedCallback disconnectedCallback(){ console.log('invoked disconnectedCallback!') } // v0 attributeChangedCallback, observedAttributes attributeChangedCallback(attrName, oldVal, newVal){ console.log(`attributeChangedCallback-change ${attrName} from ${oldVal} to ${newVal}`) } // attributeChangedCallback , static get observedAttributes(){ return ['disabled'] } // , document.adoptNode ownerDocument adoptedCallback(){ console.log('invoked adoptedCallback!') } get textContent(){ return this.querySelector('.content').textContent } set textContent(val){ this.querySelector('.content').textContent = val } close(){ this.style.display = 'none' } show(){ this.style.display = 'block' } } customElements.define('x-alert', XAlert)
  • 漸進強化
  • class XInput extends HTMLInputElement{
      constructor(){
        super()
    
        this.value = 'x-input'
      }
      isEmail(){
        const val = this.value
        return /[0-9a-zA-Z]+@\S+\.\S+/.test(val)
      }
    }
    customElements.define('x-input', XInput, {extends: 'input'})
    
    //      
    document.createElement('input', {is: 'x-input'})
    new XInput()
    
    これ以外のunresoloved状態はdefinedおよびundefined状態に変更され、CSS対応のセレクタはdocument.registerElementおよびx-alertである.また、window.customElements方法を追加して、undefinedからdefinedに変換されたイベントを傍受することができます.
    
      Twitter
      Facebook
      G+
    
    
    // Fetch all the children of  that are not defined yet.
    let undefinedButtons = buttons.querySelectorAll(':not(:defined)');
    
    let promises = [...undefinedButtons].map(socialButton => {
      return customElements.whenDefined(socialButton.localName);
    ));
    
    // Wait for all the social-buttons to be upgraded.
    Promise.all(promises).then(() => {
      // All social-button children are ready.
    });
    ちょうど使える元素を最初から定義するのは難しいです.
    ここに来て、皆さんはもうCustom Element APIを知っていると思います.以下は完全な要素をカスタマイズしてみましょう.しかし、実際の操作の前に、私たちはまず、どのような詳細に注意すべきですか?
    各段階の適切な操作を明確にする.
    1...ソースの状態を初期化したり、イベントの傍受を設定したり、Shadow Domを作成したりします.2.connectedCallbackリソースの取得や要素レンダリングなどの操作はここで実行するのに適していますが、この方法は何度も呼び出されることができますので、一回だけの操作に対しては検査方案を持参します.3.disconnectedCallback 適切な協力資源の整理などの仕事(例えば、事件の傍受を除去する)
    もっと細かいところ
    1....ソースの詳細1.1.最初の文は、:definedを呼び出して、親のインスタンスの作成を保証しなければならない1.2.:not(:defined)文がないかどうか、またはcustomeElements.whenDefined({String} tagName):Promiseまたはsuper() 1.3でなければならない.returnおよびreturn方法1.4を呼び出すことができない.要素の特性(atribute)とサブ要素にアクセスしないでください.要素はundefined状態にあるかもしれないので、特性およびサブ要素がなくても1.5にアクセスできます.要素の特性とサブ要素は設定しないでください.これは、たとえ要素がdefined状態であっても、return thisおよびdocument.writeによって要素インスタンスを作成するときに、本来は特性とサブ要素がないはずの2.focusable要素by tabindex特性 デフォルトの場合、カスタム要素はフォーカスを取得できないため、明示的にdocument.openの特性を追加してfocusableを有効にする必要があります.また、要素document.createElementnewである場合は、要素unfocusableを除去しなければならない.3.ARIA特性 ARIA特性により、他のリーダー等の他のアクセスツールにユーザー定義要素を識別することができます.4.イベントタイプ変換 は、tabindexを介してイベントを捕捉し、disabledを介してイベントを開始することによってイベントタイプを変換し、より要素の特徴に合致するイベントタイプをトリガする.trueを絞りましょう.
    class XBtn extends HTMLElement{
      static get observedAttributes(){ return ['disabled'] }
      constructor(){
        super()
    
        this.addEventListener('keydown', e => {
          if (!~[13, 32].indexOf(e.keyCode)) return  
    
          this.dispatchEvent(new MouseEvent('click', {
            cancelable: true,
            bubbles: true
          }))
        })
    
        this.addEventListener('click', e => {
          if (this.disabled){
            e.stopPropagation()
            e.preventDefault()
          }
        })
      }
      connectedCallback(){
        this.setAttribute('tabindex', 0)
        this.setAttribute('role', 'button')
      }
      get disabled(){
        return this.hasAttribute('disabled')
      }
      set disabled(val){
        if (val){
          this.setAttribute('disabled','')
        }
        else{
          this.removeAttribute('disabled')
        }
      }
      attributeChangedCallback(attrName, oldVal, newVal){
        this.setAttribute('aria-disabled', !!this.disabled)
        if (this.disabled){
          this.removeAttribute('tabindex')
        }
        else{
          this.setAttribute('tabindex', '0')
        }
      }
    }
    customElements.define('x-btn', XBtn)
    どうやってCustom Element v 1を使い始めますか?
    Chrome 54はデフォルトではCustom Element v 1をサポートしていますが、Chrome 53は起動パラメータtabindexを変更する必要があります.他のブラウザではwebcomponets.jsというpolyfillが使えます.
    余談をする
    Custom Elementについてはここまで話しましょう.でもここでちょっとおかしいですが、注意すべき細かい問題を提出します.つまり、カスタム要素は必ずaddEventListenerを使って声明しますか?dispathEventまたはx-btnの方式を採用してもいいですか?答えはだめです.カスタム要素はNormal Elementに属していますので、chrome --enable-blink-features=CustomElementsV1というスタートラベルと閉じたラベルを採用して声明しなければなりません.では何がNormal Elementですか?実は元素は以下の5種類に分けられています.
  • Void elementsフォーマットはであり、以下の要素areabasebrcolembedhrimgkeygenlink、244579142、metaparamsource、24454545454545454545454545454545454545454545454545454545454545454545454545454545454545
  • Raw text elemensフォーマットはtrackであり、以下の要素wbr
  • を含む.
  • escapable raw text elementsフォーマットはscriptであり、以下の要素style
  • を含む.
  • Foreign elemensフォーマットはtextarea、MathMLおよびSVG名前空間の要素
  • である.
  • Normal elemensフォーマットはtitleで、上記4つの要素以外の要素です.いくつかの条件の下では終了ラベルを省略してもいいです.ブラウザは自動的に補完してくれますが、結果はよく軌道に乗りますので、自分で書いたほうが安全です.
  • 締め括りをつける
    心からCustom Elementを聞いた時、私はとても興奮していました.まるで命の藁を見つけたかのようです.しかし、他の新しい技術の出現と同じように、利害が同業して、どうやって判断して利用するかは頭が痛いことです.先人の経験が方向を教えてくれるかもしれません.次の「WebComponent魔法堂:Custom Elementを深く追究し、過去から現在を見る」では、18年前にタイムスリップし、パイオニアHTML Componentの黒い歴史を見て、再びWebComponentを見ましょう. オリジナルを尊重し、転載は下記の通り明記してください.http://www.cnblogs.com/fsjohn... ^_^太ったジョンさん
    感謝します
    How to Create Custom HTML Elements A vocabulary and assicated API for HTML and XHTMLCustom Elements v 1 custom-elemens-custom-builtin-example