通过 createElement 创建 WebComponent [英] Create WebComponent through createElement

查看:51
本文介绍了通过 createElement 创建 WebComponent的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

未捕获的 DOMException:无法构造CustomElement":结果不能有子元素在 appendTodo

class TodoCard extends HTMLElement {构造函数(){极好的()this.innerHTML = `<li><div class="card"><span class="card-content">${this.getAttribute('content')}</span><i class="fa fa-circle-o";aria-hidden=真"></i><i class="fa fa-star-o";aria-hidden=真"></i>

`}}window.customElements.define('todo-card', TodoCard)const todoList = document.getElementById('todo-list')const todoForm = document.getElementById('todo-form')const todoInput = document.getElementById('todo-input')函数 appendTodo(content) {const todo = document.createElement('todo-card')todo.setAttribute('内容', 内容)todoList.appendChild(todo)}todoForm.addEventListener('提交', e => {e.preventDefault()appendTodo(todoInput.value)todoInput.value = ''})

有什么想法吗?谢谢.

解决方案

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

创建

您将看到许多示例(包括我的示例),其中在构造函数中设置了 DOM 内容.
这些元素永远不能用 document.createElement

创建

说明(HTML DOM API):H3>

使用时:

 

元素(从 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内容:

 构造函数(){super().attachShadow({mode:"open"}).innerHTML = `...`;}

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

<todo-card content=FOO></todo-card><脚本>window.customElements.define("待办事项卡",类扩展 HTMLElement {构造函数(){极好的();//this.innerHTML = this.getAttribute("content");}连接回调(){this.innerHTML = this.getAttribute("content");}});尝试 {const todo = document.createElement("todo-card");todo.setAttribute("content", "BAR");document.body.appendChild(todo);}赶上(e){控制台错误(e);}</script>

您还有一个小问题:content 一个默认属性,FireFox 不会停止警告您:

或者不使用 createElement

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

可以写成:

 const html = `

connectedCallback 可以多次运行!

当你四处移动 DOM 节点时:

<b>DO 学习:</b><todo-card todo="自定义元素 API"></todo-card>

<div id="DONT_Learn"><b>不要学习!!!</b><todo-card todo="React"></todo-card>

<脚本>window.customElements.define("待办事项卡",类扩展 HTMLElement {连接回调(){让 txt = this.getAttribute("todo");this.append(txt);//并在 DOM 移动时再次追加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 运行(注意父级!元素已经在新位置)
  • connectedCallback for LIT 再次运行,附加Learn Lit" 再次

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

Web 组件库

Lit、HyperHTML 和 Hybrids 等库实施了额外的回调来帮助解决所有这些问题.

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

有工具的傻瓜,还是傻瓜

I'm having an issue creating a Web Component using createElement. I'm getting this error:

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children at 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 = ''
})

any ideas? Thanks.

解决方案

A Custom Element that sets DOM content in the constructor
can never be created with document.createElement()

You will see many examples (including from me) where DOM content is set in the constructor.
Those Elements can never be created with document.createElement

Explanation (HTML DOM API):

When you use:

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

The element (extended from HTMLElement) has all the HTML interfaces (it is in a HTML DOM),
and you can set the innerHTML in the constructor

But, when you do:

  document.createElement("todo-card");

The constructor runs, without HTML interfaces (the element may have nothing to do with a DOM),
thus setting innerHTML in the constructor produces the error:

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children

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

The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods. In general, work should be deferred to connectedCallback as much as possible

shadowDOM is a DOM

When using shadowDOM you can set shadowDOM content in the constructor:

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

Correct code (no shadowDOM): use the connectedCallback:

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

<script>
  window.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>

You have another minor issue: content was a default attribute, and FireFox won't stop warning you:

Or don't use createElement

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

can be written as:

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

The connectedCallback can run multiple times!

When you move DOM nodes around:

<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>
  window.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 runs for LIT
  • when LIT is moved
  • disconnectedCallback runs (note the parent! The Element is already in the new location)
  • connectedCallback for LIT runs again, appending "Learn Lit" again

It is up to you the programmer how your component/application must handle this

Web Component Libraries

Libraries like Lit, HyperHTML and Hybrids have extra callbacks implemented that help with all this.

I advice to learn the Custom Elements API first, otherwise you are learning a tool and not the technology.

And a Fool with a Tool, is still a Fool

这篇关于通过 createElement 创建 WebComponent的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆