[ 命名槽位(named slot)]
通过匹配的 slot/name 属性对实现的,比如下面的例子中,带有 slot="foo" 属性的元素会被投射到带有 name="foo" 的 <slot>
上.
document.body.innerHTML = ` <div> <p slot="foo">Foo</p> <p slot="bar">Bar</p> </div> `; let shadowDom = document.querySelector('div').attachShadow({ mode: 'open' }); shadowDom.innerHTML = ` <slot name="bar"></slot> <slot name="foo"></slot> `; 复制代码
影子 DOM 中的事件重定向
如果影子 DOM 中发生了浏览器事件(如 click),那么浏览器需要一种方式以让父 DOM 处理事件.
注意:事件重定向只会发生在影子 DOM 中实际存在的元素上,使用
<slot>
标签从外部投射进来的元素不会发生事件重定向,因为从技术上讲,这些元素仍然存在于影子 DOM 外部。
// 创建一个元素作为影子宿主 document.body.innerHTML = ` <div onclick="console.log('Handled outside:', event.target)"></div> `; // 添加影子 DOM 并向其中插入 HTML document.querySelector('div') .attachShadow({ mode: 'open' }) .innerHTML = ` <button onclick="console.log('Handled inside:', event.target)">Foo</button> `; // 点击按钮时: // Handled inside: <button onclick="..."></button> // Handled outside: <div onclick="..."></div> 复制代码
创建影子 DOM 的限制
考虑到安全及避免影子 DOM 冲突,并非所有元素都可以包含影子 DOM,尝试给无效元素或者已经有了影子 DOM 的元素添加影子 DOM 会导致抛出错误。
以下是可以容纳影子 DOM 的元素:
<任何以有效名称创建的自定义元素> <article> <aside> <blockquote> <body> <div> <footer> <h1> <h2> <h3> <h4> <h5> <h6> <header> <main> <nav> <p> <section> <span> 复制代码
自定义元素
自定义元素为 HTML 元素引入了面向对象编程的风格,基于这种风格,可以创建自定义的、复杂的和可重用的元素,而且只要使用简单的 HTML 标签或属性就可以创建相应的实例。
创建自定义元素
浏览器会尝试将无法识别的元素作为通用元素整合进 DOM,如下面的例子所示.
自定义元素在此基础上更进一步,利用自定义元素,可以在 <x-box>
标签出现时为它定义复杂的行为,同样也可以在 DOM 中将其纳入元素生命周期管理。
document.body.innerHTML = ` <x-box>I'm inside a custom element.</x-box> `; console.log(document.querySelector('x-box') instanceof HTMLElement); // true 复制代码
customElements.define()
调用 customElements.define()
方法可以创建自定义元素,自定义元素的强大之处在于类定义.
class BoxElement extends HTMLElement { constructor() { super(); console.log('x-box') } } customElements.define('x-box', BoxElement); document.body.innerHTML = ` <x-box></x-box> <x-box></x-box> <x-box></x-box> `; // x-box // x-box // x-box 复制代码
is 和 extents
如果自定义元素继承了一个元素类,那么可以使用 is
属性和 extends
选项将标签指定为该自定义元素的实例.
class BoxElement extends HTMLDivElement { constructor() { super(); console.log('x-box') } } customElements.define('x-box', BoxElement, { extends: 'div' }); document.body.innerHTML = ` <div is="x-box"></div> <div is="x-box"></div> <div is="x-box"></div> `; // x-box // x-box // x-box 复制代码
添加自定义元素内容
class BoxElement extends HTMLElement { constructor() { super(); // 方式一 this.innerHTML = `<div>this is content1!</div>`; // 方式二 let div = document.createElement('div'); div.innerText = `this is content2!`; this.appendChild(div); // 方式三 this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <p>I'm inside a custom element!</p> `; } } customElements.define('x-box', BoxElement); document.body.innerHTML += `<x-box></x-box`; 复制代码
使用自定义元素生命周期方法
自定义元素有以下 5 个生命周期方法:
- constructor():在创建元素实例或将已有 DOM 元素升级为自定义元素时调用
- connectedCallback():在每次将这个自定义元素实例添加到 DOM 中时调用
- disconnectedCallback():在每次将这个自定义元素实例从 DOM 中移除时调用
- attributeChangedCallback():在每次可观察属性的值发生变化时调用,在元素实例初始化时,初始值的定义也算一次变化
- adoptedCallback():在通过 document.adoptNode()将这个自定义元素实例移动到新文档
class BoxElement extends HTMLElement { constructor() { super(); console.log('ctor'); } connectedCallback() { console.log('connected'); } disconnectedCallback() { console.log('disconnected'); } } customElements.define('x-box', BoxElement); const BoxElement = document.createElement('x-box'); // ctor document.body.appendChild(BoxElement); // connected document.body.removeChild(BoxElement); // disconnected 复制代码
升级自定义元素
并非始终可以先定义自定义元素,然后再在 DOM 中使用相应的元素标签。可以通过 Web 组件在 CustomElementRegistry 上的一些方法,用来检测自定义元素是否定义完成,然后可以用它来升级已有元素。
- 如果自定义元素已经有定义,那么 CustomElementRegistry.get() 方法会返回相应自定义元素的类
- CustomElementRegistry.whenDefined() 方法会返回一个期约,当相应自定义元素有定义之后解决
customElements.whenDefined('x-box').then(() => console.log('defined!')); console.log(customElements.get('x-box')); // undefined customElements.define('x-box', class {});// defined! console.log(customElements.get('x-box'));// class BoxElement {} 复制代码
连接到 DOM 的元素在自定义元素有定义时会自动升级,如果想在元素连接到 DOM 之前强制升级,可以使用 CustomElementRegistry.upgrade() 方法.
// 在自定义元素有定义之前会创建 HTMLUnknownElement 对象 const boxElement = document.createElement('x-box'); // 创建自定义元素 class BoxElement extends HTMLElement {} customElements.define('x-box', BoxElement); console.log(boxElement instanceof BoxElement); // false // 强制升级 customElements.upgrade(boxElement); console.log(boxElement instanceof BoxElement); // true 复制代码
总结
到现在,你应该发现了 web 组件中的一些内容和 vue 中的某些内容是不是很相似,如:
vuejs
<template>
标签<slot>
插槽<style scope>
局部样式vue
组件,即.vue
文件<component is="name">
动态组件
web 组件
template
模板- 影子 DOM 中的
<slot>
插槽 - 影子 DOM 中的
<style>
隔离样式 - 自定义标签,即
自定义组件
- 自定义组件中的
is
属性和extends
选项
注意:当然相似归相似,其本质还是不同的,所以千万不要进行混淆,但是可以认为概念上是有所借鉴的,至此不难发现很多知识内容其实是相通的。