Web Components 可以简单地理解为一种自定义 HTML 元素和组件的技术,它由三个技术组成:Custom Element、Shadow DOM 和 HTML Templates。一个 Web Component 首先要通过 Custom Element 定义,并且可以使用 Shadow DOM 来封装它的样式和行为。而通过 HTML Templates,我们可以预定义组件的 HTML 结构,这样就可以将组件进行复用。
Web Components 作为一种组件化的技术,最重要的优势之一就是组件树的嵌套。组件树是由多个组件组成的层级结构,由此实现了对 Web 页面的语义化组织。这种嵌套方式是通过 Slot 实现的,本文将介绍 Web Components 如何通过 Slot 实现组件树的嵌套。
Slot 简介
Slot 是 Shadow DOM 的一个重要特性,它可以让开发者将一段 HTML 代码“插入”到 Shadow DOM 中。在 Custom Element 中,通过定义 元素可以在 Shadow DOM 中划定出一个“挂载点”(Slot),我们可以在 Custom Element 中使用这个 Slot,来将外部传入的 HTML 挂载到这个位置。
假设我们在一个 Custom Element 中定义了以下 Shadow DOM:
<template> <style> h2 { color: red; } </style> <h2><slot></slot></h2> </template>
在这个 Shadow DOM 中,我们定义了一个 h2 元素,并将 元素作为它的子元素。此时这个 Custom Element 的模板就定义了一个挂载点,并且在 Shadow DOM 中使用了这个挂载点,这个 Custom Element 就可以使用使用这个 Slot 去渲染外部传入的信息。
<my-element> <div>Hello World!</div> </my-element>
这里,我们在 Constructor 中添加初始化 Shadow DOM 模板的逻辑:
class MyElement extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <style> h2 { color: red; } </style> <h2><slot></slot></h2> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-element', MyElement);
通过上述代码,我们可以发现 Cusomt Element 的内部已经可以通过 Slot 让外部传入的 HTML 输出到自己的 Shadow DOM 中了。
内置 Slot 的使用
在 Web Components 的组件树结构中,内置 Slot 还有两个重要的用处:
- 组件的默认“挂载点”
- 父子组件之间的传值
1. 组件的默认“挂载点”
在 Custom Element 中,我们可以通过内置 Slot 为组件定义默认的“挂载点”,这样当使用这个组件并且没有向它传递任何内容时,就可以输出组件默认的内容。
<my-component></my-component>
class MyComponent extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <p>Default Content</p> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent);
在这个例子中,当我们没有向 MyComponent 传递任何内容时,它就会输出默认的
Default Content
。2. 父子组件之间的传值
Slot 除了用来插入外部 HTML,还可以用于组件之间的传值。这就是我们在 Web Components 中经常遇到的父组件向子组件传值的方式之一。我们可以通过将一些组件功能封装到 Custom Element 中,然后将子组件作为 Slot 的内容注入进去,达到实现父组件向子组件传值的效果。
我们可以通过定义子组件的装饰器 @slot 来实现这个过程。装饰器可以看作带有特殊行为的函数,我们可以在 @slot 中定义子组件的内容,并作为 Slot 在 Shadow DOM 中进行渲染:
<my-component> <my-sub-component>Sub Component Content</my-sub-component> </my-component>
class MyComponent extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <h2><slot name="title">Default Title</slot></h2> <p><slot name="content"></slot></p> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent); class MySubComponent extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <style> p { color: red; } </style> <p><slot></slot></p> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-sub-component', MySubComponent);
在这个例子中,我们首先定义了一个父组件 MyComponent,并在其中定义了两个 Slot,分别用于渲染标题和内容。在子组件 MySubComponent 中,我们再次定义了一个 Slot,并向 MyComponent 传递需要放入子组件的内容。这时,在 MyComponent 中,我们通过将 MySubComponent 插入到第二个 Slot 中,实现了父子组件间的传值功能。
自定义 Slot 的使用
除了默认的 Slot,我们还可以通过定义自定义 Slot 实现更灵活的传值。
假设我们需要实现一个可以渲染任务列表的应用,任务列表由多个任务项组成,每个任务项中包含任务的标题和详细内容,我们需要通过 Web Components 将这个任务列表组合到一起。
首先,我们定义任务项组件 TaskItem,并在其中使用自定义 Slot 来渲染任务:
<task-item> <div slot="title">Title</div> <div slot="content">Content</div> </task-item>
class TaskItem extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <div class="task-item"> <h3><slot name="title"></slot></h3> <p><slot name="content"></slot></p> </div> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('task-item', TaskItem);
在定义 TaskItem 组件时,我们定义了两个自定义的 Slot:Slot name 分为 title 和 content。这样,我们在定义 TaskItem 时就能够通过以下的方式将任意内容放入到这两个 Slot 中。
在这个例子中,我们使用两个自定义 Slot 分别渲染了一个任务项的任务标题和详细内容,接下来我们可以通过使用灵活的自定义 Slot 在多个任务项中传值,以实现整个任务列表的绘制:
<my-task-list> <task-item> <div slot="title">Task 1 Title</div> <div slot="content">Task 1 Content</div> </task-item> <task-item> <div slot="title">Task 2 Title</div> <div slot="content">Task 2 Content</div> </task-item> ... </my-task-list>
class MyTaskList extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.createElement('template'); template.innerHTML = ` <div class="task-list"> <slot></slot> </div> `; shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-task-list', MyTaskList);
在这个例子中,我们定义了一个包含多个 TaskItem 的 MyTaskList,并通过自定义 Slot 构造实现了多个 TaskItem 间的传值功能。在 MyTaskList 的模板中,我们仅仅使用了一个默认 Slot 来渲染这些 TaskItem,并使用 Slot 元素来将 TaskItem 插入到了 Shadow DOM 中。这样,我们就能够实现一个简单的任务列表组件。
总结
本文介绍了 Web Components 如何通过 Slot 实现组件树的嵌套,包括:
- Slot 的定义和基本用法
- 内置 Slot 的使用方法(默认内容和传值)
- 自定义 Slot 的使用方法(灵活传值)
Web Components 的组件化方案可以实现更为灵活的前端组件开发,并且基于标准化的 HTML、CSS 和 JavaScript 让我们能够在项目中更好地复用和组装组件。通过本文的学习,我们相信可以更好地理解 Web Components 的部分内容,进而能够更为熟练地开发基于 Web Components 的组件技术。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65b4d7f3add4f0e0ffdb3b1e