【问题标题】:Creating new instances of custom HTML elements创建自定义 HTML 元素的新实例
【发布时间】:2018-10-28 23:32:41
【问题描述】:

我正在尝试了解新的 HTML 自定义元素。

我的目标是,给定一些数据数组,创建自定义元素的 n 个实例。例如,给定一个包含 10 个用户的列表,创建 10 个用户 html 对象。

好的 - 所以我在 html 中定义了一个自定义元素

HTML

<template-user>
   <div class="user-name"></div>
</template-user>

然后我创建我的控制器

JS

class UserTemplate extends HTMLElement {
   constructor(){
      super();
      this.username = this.querySelectorAll('[class="user-name"]')[0];
   }
   setName(name){
      this.username.innerHtml = name;
   }
}
customElements.define('template-user', UserTemplate);

页面加载正常,但现在我对如何重用该元素感到困惑。如果我在做普通的老学校的事情,我会有一个 for 循环抽出一些 HTML 字符串并设置一些东西的 innerHTML。但现在我宁愿做类似的事情

for(let i = 0; i < users.length; i++){
   let userTemplate = new UserTemplate();
   userTemplate.setName(user.name);
   // append to user list, etc..
}

当我尝试这样做时,它似乎几乎可以工作。但它找不到username,即this.querySelectorAll 将返回null。仅当我尝试构造此元素的新实例时。那么,我应该如何创建新的自定义元素 DOM 对象?

【问题讨论】:

标签: javascript web-component custom-element


【解决方案1】:

确保您了解 Web 组件的构造函数的要求和限制:


https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance

4.13.2 自定义元素构造函数的要求

在创作自定义元素构造函数时,作者受以下一致性要求的约束:

  • 对 super() 的无参数调用必须是构造函数主体中的第一条语句,以便在运行任何其他代码之前建立正确的原型链和此值。
  • return 语句不得出现在构造函数主体内的任何位置,除非它是简单的提前返回(return 或 return this)。
  • 构造函数不得使用 document.write() 或 document.open() 方法。
  • 不得检查元素的属性和子元素,因为在非升级的情况下不会出现任何内容,并且依赖升级会降低元素的可用性。
  • 元素不得获得任何属性或子元素,因为这违背了使用 createElement 或 createElementNS 方法的消费者的期望。
  • 一般而言,工作应尽可能推迟到 connectedCallback,尤其是涉及获取资源或渲染的工作。但是请注意,connectedCallback 可以多次调用,因此任何真正一次性的初始化工作都需要一个守卫来防止它运行两次。
  • 一般来说,构造函数应该用于设置初始状态和默认值,并设置事件侦听器和可能的影子根。

在元素创建过程中会直接或间接检查其中的一些要求,如果不遵循这些要求,将导致解析器或 DOM API 无法实例化自定义元素。即使工作是在构造函数启动的微任务中完成也是如此,因为微任务检查点可以在构造后立即发生。


您可以进行类似的更改:

class TemplateUser extends HTMLElement {
  static get observedAttributes() {
    return ['user-name'];
  }

  constructor(){
    super();
    this.attachShadow({mode:'open'});
    this.shadowRoot.innerHTML = '<div></div>';
  }
   
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      this.shadowRoot.firstChild.innerHTML = newVal;
    }
  }
 
  get userName() {
    return this.getAttribute('user-name');
  }
  
  set userName(name) {
    this.setAttribute('user-name', name);
  }
}

customElements.define('template-user', TemplateUser);


setTimeout( function () {
  var el = document.querySelector('[user-name="Mummy"]');
  el.userName = "Creature from the Black Lagoon";
}, 2000);
<template-user user-name="Frank N Stein"></template-user>
<template-user user-name="Dracula"></template-user>
<template-user user-name="Mummy"></template-user>

这使用 shadowDOM 存储&lt;div&gt;,然后您通过user-name 属性或userName 属性设置值。

【讨论】:

    【解决方案2】:

    但是找不到用户名,即this.querySelectorAll会返回null。

    当你创建一个新实例时,新元素没有子元素,所以querySelectorAll 将返回一个空的 NodeList。如果您查询 DOM 并选择已在标记中定义的 template-user,则 username 属性将引用您的 div 元素。

    如果您希望动态生成的template-user 元素默认具有&lt;div class="user-name"&gt;&lt;/div&gt; 子元素,则应在构造函数中创建并附加一个元素。

    对于选择第一个匹配元素,您可以使用.querySelector(...) 而不是.querySelectorAll(...)[0]

    【讨论】:

      【解决方案3】:

      当您通过 Javascript 使用 new 创建自定义元素时,您可以设置一些变量,这些变量将在 constrcutor() 方法中作为参数传递:

      for (let user of users) {
         document.body.appendChild( new UserTemplate(user.name) )
      }
      

      您可以获取它并将其保存在对象变量中,或者在 Shadow DOM 中用作模板文字字符串中的变量。

      class UserTemplate extends HTMLElement {
          constructor(username){
              super()
              //this.username = username
              this.attachShadow({ mode:'open' })
                  .innerHTML = `<div class="user-name"> ${username} </div>` 
          }
      }
      customElements.define('template-user', UserTemplate);
      

      【讨论】:

        【解决方案4】:

        您可以直接在 JS 中创建组件并分配属性

        let templateUser = document.createElement('template-user');
        templateUser.userName= 'Your name here';
        document.body.appendChild(templateUser);
        

        或基于您的需要的类似内容。 谷歌有一些文档here,大约是他们描述如何“在 JavaScript 中创建实例”的 2/3。这可能非常强大,尤其是在像您的示例这样的 for 循环中

        【讨论】:

          猜你喜欢
          • 2016-12-31
          • 2017-02-20
          • 1970-01-01
          • 2013-06-19
          • 1970-01-01
          • 1970-01-01
          • 2020-11-13
          • 1970-01-01
          相关资源
          最近更新 更多