通过createElement创建WebComponent

问题描述 投票:0回答:2

我在使用 createElement 创建 Web 组件时遇到问题。我收到此错误:

未捕获的 DOMException:无法构造“CustomElement”:结果不得有子级 在appendTodo

class TodoCard extends HTMLElement {
    constructor() {
        super()

        this.innerHTML = `
            <li>
                <div class="card">
                    <span class="card-content">${this.getAttribute('content')}</span>
                    <i class="fa fa-circle-o" aria-hidden="true"></i>
                    <i class="fa fa-star-o" aria-hidden="true"></i>
                </div>
            </li>
        `
    }
}

window.customElements.define('todo-card', TodoCard)

const todoList = document.getElementById('todo-list')
const todoForm = document.getElementById('todo-form')
const todoInput = document.getElementById('todo-input')

function appendTodo(content) {
    const todo = document.createElement('todo-card')
    todo.setAttribute('content', content)
    todoList.appendChild(todo)
}

todoForm.addEventListener('submit', e => {
    e.preventDefault()
    appendTodo(todoInput.value)
    todoInput.value = ''
})

有什么想法吗? 谢谢。

javascript web-component
2个回答
16
投票

constructor

中设置 DOM 内容的自定义元素 永远不能用
document.createElement()

创建

你会看到很多例子(包括我自己的),其中 DOM 内容是在构造函数中设置的。
这些元素永远无法用

document.createElement

创建

说明(HTML DOM API):

使用时:

  <todo-card content=FOO></todo-card>

元素(从 HTMLElement 扩展)具有所有 HTML 接口(位于 HTML DOM 中),
可以构造函数

中设置innerHTML

但是,当你这样做时:

  document.createElement("todo-card");

构造函数运行,没有HTML接口(该元素可能与DOM无关),
因此在构造函数中设置innerHTML会产生错误:

未捕获的 DOMException:无法构造“CustomElement”:结果不能有子元素

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

该元素不得获得任何属性或子元素,因为这违反了使用 createElement 或 createElementNS 方法的使用者的期望。 一般来说,工作应该尽可能推迟到connectedCallback

shadowDOM 是一个 DOM

使用 shadowDOM 时,您可以构造函数中设置 shadowDOM 内容:

  constructor(){
    super().attachShadow({mode:"open"})
           .innerHTML = `...`;
  }

正确的代码(无shadowDOM):使用
connectedCallback

<todo-card content=FOO></todo-card>

<script>
  customElements.define(
    "todo-card",
    class extends HTMLElement {
      constructor() {
        super();
        //this.innerHTML = this.getAttribute("content");
      }
      connectedCallback() {
        this.innerHTML = this.getAttribute("content");
      }
    }
  );

  try {
    const todo = document.createElement("todo-card");
    todo.setAttribute("content", "BAR");
    document.body.appendChild(todo);
  } catch (e) {
    console.error(e);
  }
</script>

您还有另一个小问题:

content
默认属性,FireFox 不会停止警告您:

或者不要使用createElement

  const todo = document.createElement("todo-card");
  todo.setAttribute("content", "BAR");
  document.body.appendChild(todo);

可以写成:

  const html = `<todo-card content="BAR"></todo-card`;
  document.body.insertAdjacentHTML("beforeend" , html); 

connectedCallback
可以运行多次!

当你移动 DOM 节点时:

<div id=DO_Learn>
  <b>DO Learn: </b><todo-card todo="Custom Elements API"></todo-card>
</div>
<div id="DONT_Learn">
  <b>DON'T Learn!!! </b><todo-card todo="React"></todo-card>
</div>
<script>
  customElements.define(
    "todo-card",
    class extends HTMLElement {
      connectedCallback() {
        let txt = this.getAttribute("todo");
        this.append(txt);// and appended again on DOM moves
        console.log("qqmp connectedCallback\t", this.parentNode.id, this.innerHTML);
      }
      disconnectedCallback() {
        console.log("disconnectedCallback\t", this.parentNode.id , this.innerHTML);
      }
    }
  );
  const LIT = document.createElement("todo-card");
  LIT.setAttribute("todo", "Lit");
  DO_Learn.append(LIT);
  DONT_Learn.append(LIT);
</script>

  • connectedCallback 为 LIT 运行
  • 当LIT移动时
  • disconnectedCallback 运行(注意父级!元素已经位于新位置)
  • LIT 的
  • connectedCallback 再次运行,并再次追加
    "Learn Lit"

由程序员决定你的组件/应用程序必须如何处理这个问题

Web 组件库

Lit、HyperHTML 和 Hybrids 等库实现了额外的回调,可以帮助解决所有这些问题。

我建议先学习自定义元素API,否则你学习的是工具而不是技术。

傻瓜有了工具,仍然是傻瓜

另请阅读我在

connectedCallback
上发布的 Dev.文章: https://dev.to/dannyengelman/web-component-developers-do-not-connect-with-the-connectedcallback-yet-4jo7


0
投票

您在插入 DOM 之前定义自定义元素,您必须遵循以下步骤

  1. 使用 document.createElement("todo-card")创建自定义元素
  2. 使用 document.body.appendChild(todo) 将创建的元素追加到 DOM
  3. 然后最后使用 window.customElements.define('todo-card', TodoCard)
  4. 定义自定义元素

下面的代码片段对我很有用

class TodoCard extends HTMLElement {
    constructor() {
        super()

        this.innerHTML = `
            <li>
                <div class="card">
                    <span class="card-content">My Component</span>
                    <i class="fa fa-circle-o" aria-hidden="true"></i>
                    <i class="fa fa-star-o" aria-hidden="true"></i>
                </div>
            </li>
        `
    }
}

const todoList = document.getElementById('todo-list')

function appendTodo() {
    const todo = document.createElement('todo-card')
    todoList.appendChild(todo)
    window.customElements.define('todo-card', TodoCard)
}

appendTodo();
<div id="todo-list"></div>

© www.soinside.com 2019 - 2024. All rights reserved.