Svelte 组件也可以使用 customElement: true 编译器选项编译为自定义元素(也称为 Web 组件)。您应该使用 <svelte:options> 元素 为组件指定一个标签名称。

  1. <svelte:options customElement="my-element" />
  2. <!-- 在 Svelte 3 中,这样做:
  3. <svelte:options tag="my-element" />
  4. -->
  5. <script>
  6. export let name = 'world';
  7. </script>
  8. <h1>Hello {name}!</h1>
  9. <slot />

您可以省略您不想公开的任何内部组件的标签名称,并像使用常规 Svelte 组件一样使用它们。如果需要,组件的用户之后可以使用静态 element 属性对其进行命名,该属性包含自定义元素构造函数,并且在 customElement 编译器选项为 true 时可用。

  1. import MyElement from './MyElement.svelte';
  2. customElements.define('my-element', MyElement.element);
  3. // 在 Svelte 3 中,这样做:
  4. // customElements.define('my-element', MyElement);

一旦自定义元素被定义,它就可以像常规 DOM 元素一样使用:

  1. document.body.innerHTML = `
  2. <my-element>
  3. <p>This is some slotted content</p>
  4. </my-element>
  5. `;

默认情况下,自定义元素使用 accessors: true 编译,这意味着任何 props 都作为 DOM 元素的属性公开(并且在可能的情况下,作为属性可读/可写)。

要阻止这种行为,在 <svelte:options> 中添加 accessors={false}

  1. const el = document.querySelector('my-element');
  2. // 获取 'name' prop 的当前值
  3. console.log(el.name);
  4. // 设置一个新值,更新 shadow DOM
  5. el.name = 'everybody';

组件生命周期

使用 Svelte 编写的自定义元素使用包装器方法创建。这意味着内部的 Svelte 组件并不知道它是一个自定义元素。自定义元素包装器负责适当地处理其生命周期。

当创建自定义元素时,它包装的 Svelte 组件不会立即创建。它仅在调用 connectedCallback 后的下一个滴答中创建。在将其插入 DOM 之前分配给自定义元素的属性将被暂时保存,然后在组件创建时设置,这样它们的值就不会丢失。但是,对于调用自定义元素上导出的函数则不适用,它们只有在元素挂载后才可用。如果您需要在组件创建之前调用函数,可以使用 extend 选项 来解决。

当使用 Svelte 编写的自定义元素被创建或更新时,shadow DOM 将在下一个滴答中反映该值,而不是立即。这样更新可以批量处理,并且 DOM 的移动(即使暂时(但同步)地将元素从 DOM 中分离)不会导致内部组件卸载。

内部 Svelte 组件在调用 disconnectedCallback 后的下一个滴答中被销毁。

组件选项

在构建自定义元素时,您可以通过在 Svelte 4 中的 <svelte:options> 内定义 customElement 为对象来定制几个方面。此对象可能包含以下属性:

  • tag: 自定义元素名称的强制性 tag 属性
  • shadow: 一个可选属性,可以设置为 "none" 以放弃创建 shadow root。注意,样式将不再封装,并且您不能使用插槽
  • props: 一个可选属性,用于修改组件属性的某些细节和行为。它提供以下设置:
    • attribute: string: 更新自定义元素的 prop,您有两种选择:要么像上面所示的那样在自定义元素的引用上设置属性,要么使用 HTML 属性。默认情况下,属性名称是小写属性名称。通过分配 attribute: "<desired name>" 来修改它。
    • reflect: boolean: 默认情况下,更新的 prop 值不会反射回 DOM。要启用此行为,请设置 reflect: true
    • type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object': 在将属性值转换为 prop 值并反射回去时,默认假定 prop 值是 String。这可能不总是准确的。例如,对于数字类型,使用 type: "Number" 定义。您不需要列出所有属性,未列出的将使用默认设置。
  • extend: 一个可选属性,期望一个函数作为其参数。它传递由 Svelte 生成的自定义元素类,并期望您返回一个自定义元素类。如果您对自定义元素的生命周期有非常特定的要求,或者想要增强类以使用 ElementInternals 以获得更好的 HTML 表单集成,这将非常有用。
  1. <svelte:options
  2. customElement={{
  3. tag: 'custom-element',
  4. shadow: 'none',
  5. props: {
  6. name: { reflect: true, type: 'Number', attribute: 'element-index' }
  7. },
  8. extend: (customElementConstructor) => {
  9. // 扩展类,以便我们可以让它参与 HTML 表单
  10. return class extends customElementConstructor {
  11. static formAssociated = true;
  12. constructor() {
  13. super();
  14. this.attachedInternals = this.attachInternals();
  15. }
  16. // 在这里添加函数,而不是在组件中添加,以便它总是可用的,而不仅仅是在内部 Svelte 组件挂载时
  17. randomIndex() {
  18. this.elementIndex = Math.random();
  19. }
  20. };
  21. }
  22. }}
  23. />

注意事项和限制

自定义元素可以是将组件打包用于非 Svelte 应用程序的有用方式,因为它们将与纯 HTML 和 JavaScript 以及 大多数框架 一起工作。然而,有一些重要的差异需要注意:

  • 样式是 encapsulated(封装的),而不仅仅是 scoped(作用域化的)(除非您设置 shadow: "none")。这意味着任何非组件样式(例如您可能在 global.css 文件中拥有的样式)将不适用于自定义元素,包括带有 :global(...) 修饰符的样式
  • 样式不会被提取为单独的 .css 文件,而是作为 JavaScript 字符串内联到组件中
  • 自定义元素通常不适合用于服务器端渲染,因为 shadow DOM 在 JavaScript 加载之前是不可见的
  • 在 Svelte 中,插槽内容渲染是 lazily(惰性地)。在 DOM 中,它是 eagerly(急切地)渲染的。换句话说,即使组件的 <slot> 元素在 {#if ...} 块内,它也会被创建。同样,将 <slot> 包括在 {#each ...} 块中也不会导致插槽内容被多次渲染
  • let: 指令没有效果,因为自定义元素没有将数据传递给填充插槽的父组件的方式
  • 需要 polyfills 来支持旧版浏览器
  • 您可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能跨自定义元素使用它们。换句话说,您不能在父自定义元素上使用 setContext 并在子自定义元素中使用 getContext 读取它。