前言
- 如果你是用vue,react这些框架,就会很熟悉自定义元素的概念.
- 自定义元素用面向对象风格,可创建自定义的,复杂的,可重用的元素.
一、自定义元素是什么
- 浏览器会将无法识别的元素整合进DOM,但这些元素也不会起到任何作用.
- 而自定义的标签会标称HTMLElement实例.
1 乱写的标签
document.body.innerHTML = `
<x-foo>这是随便写的</x-foo>
`;
console.log(document.querySelector('x-foo') instanceof HTMLElement); // true
console.log(HTMLElement); // ƒ HTMLElement() { [native code] }
二、使用步骤
1 自定义标签
- 如果给x-foo定义复杂的行为,也将其纳入DOM的声明周期管理.
- 需要使用customElements.define( ) 创建自定义元素.
基本的定义
class FooElement extends HTMLElement { }
// 注册为自定义标签
customElements.define('x-foo', FooElement);
document.body.innerHTML = `<x-foo>自定义元素</x-foo>`;
console.log(document.querySelector('x-foo') instanceof FooElement); // true
在页面中引用自定义元素
// 自定义元素的类中继承了HTMLElement必须先调用super()
class FooElement extends HTMLElement {
constructor() {
super();
console.log('x-foo');
}
}
customElements.define('x-foo', FooElement);
document.body.innerHTML = `
<x-foo> 11</x-foo>
<x-foo>22 </x-foo>
<x-foo>33 </x-foo>`;
2 添加web组件内容
- 每次将自定义元素添加到DOM中,都会调用其类的构造函数
- 直接给自定义元素添加DOM,会抛出错误.
- 但可以为自定义元素添加影子DOM
class FooElement extends HTMLElement {
constructor() {
super();
// this表示当前的自定义标签实例
// 设置为影子DOM
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<p>自定义元素</p>`;
}
}
customElements.define('x-foo', FooElement);
document.body.innerHTML += '<x-foo> </x-foo>'
3结合template模板标签使用
// html代码
<template id="x-foo-tpl">
<p>template模板的内容</p>
</template>
// js代码
const template = document.querySelector('#x-foo-tpl');
class FooElement extends HTMLElement {
constructor() {
super();
console.log(this);
// 设置自定义标签为影子DOM
this.attachShadow({ mode: 'open' });
// 克隆template中的内容给自定标签
this.shadowRoot.appendChild(template.content.cloneNode(true))
}
}
customElements.define('x-foo', FooElement);
document.body.innerHTML += '<x-foo> </x-foo>';
4 自定义元素的生命周期方法
- constructor() 创建自定元素或将已有DOM升级为自定义DOM时调用.
- connectedCallback() 每次将自定义元素添加到DOM中时,调用
- disconnectedCallback() 将自定义元素,移除时调用
- attrbuteChangeCallback() 在属性值发生变化时调用,初始值的设置也算一次变化.
- adoptedCallback() 通过docunment.adopNode() 将自定义元素移动到新文档时调用.
class FooElement extends HTMLElement {
constructor() {
super();
console.log('被创建..');
}
connectedCallback() {
console.log('添加到DOM');
}
disconnectedCallback() {
console.log('被从DOM中移除');
}
}
// customElements.define('x-foo', FooElement);
// 创建
const fooTag = document.createElement('x-foo');
// 追加到DOM中
document.body.appendChild(fooTag);
// 移除
document.body.removeChild(fooTag);
声明周期方法,可以让我们在自定义标签的每个阶段,进行不同的操作.
三, 反射自定义属性
- 自定义属性是DOM实体又是js对象,两者之间的变化应该是同步的,
- 对DOM的修改应该映射到js对象中.
- 对js对象的修改也应该映射到DOM中.
修改js对象的值,映射到DOM中
document.body.innerHTML = '<x-foo>自定义元素</x-foo>';
class FooElement extends HTMLElement {
constructor() {
super();
this.bar = true
}
get bar() {
return this.getAttribute('bar')
}
set bar(val) {
this.setAttribute('bar', val);
console.log('被设置属性...');
}
}
customElements.define('x-foo', FooElement);
// console.log(document.body.innerHTML);
// 获取标签
const foo = document.querySelector('x-foo');
// console.log(foo);
// 重新设置属性
foo.setAttribute('bar', 'hello')
DOM映射到js对象
class FooElement extends HTMLElement {
// 监听自定义元素属性
static get observedAttributes() {
// 返回应该触发 attributeChangeCallback 执行的属性
return ['bar']
}
get bar() {
return this.getAttribute('bar')
}
set bar(val) {
this.setAttribute('bar', val);
console.log('被设置属性...');
}
// observedAttributes() 监听到变化,则通知该方法
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
console.log('修改之前的值', oldValue);
console.log('修改之后的值', newValue);
this[name] = newValue;
}
}
}
customElements.define('x-foo', FooElement);
document.body.innerHTML = `<x-foo bar="false">初始设置 </x-foo>`;
document.querySelector('x-foo').setAttribute('bar', '小甜甜')
这里使用的set和get都是,ES6中新的特性.
总结
- 自定义标签,主要是用class定义,继承自HTMLElement,构造函数中使用super调用父类的构造方法.
- 自定义标签类,必须用 customElements.define()进行注册.
- 给自定义标签中,添加内容,可以用影子组件,也可以用template模板标签.
别跑,据说给我一键三联的人写代码都没Bug! 您的支持就是我最大的动力!
我是飞翔,
愿您日有所长!