Svelte 最新中文文档教程(21)—— 自定义元素
前言
Svelte,一个语法简洁、入门容易,面向未来的前端框架。
从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:
Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构建轻量级 Web 项目。
为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。
如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。
自定义元素
Svelte 组件也可以使用 customElement: true
编译器选项编译为自定义元素(又称 Web 组件)。你应该使用 <svelte:options>
元素 为组该件指定一个标签名。
<svelte:options customElement="my-element" />
<script>
let { name = 'world' } = $props();
</script>
<h1>Hello {name}!</h1>
<slot />
对于那些你不想暴露的内部组件,你可以省略标签名,并像使用常规 Svelte 组件一样使用它们。如果需要,组件的使用者仍然可以在之后通过静态 element
属性来命名它,该属性包含自定义元素构造函数,并且在 customElement
编译器选项为 true
时可用。
// @noErrors
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
一旦自定义元素被定义,它就可以作为常规 DOM 元素一样使用:
document.body.innerHTML = `
<my-element>
<p>这是一些插槽内容</p>
</my-element>
`;
任何 props 都会作为 DOM 元素的属性暴露出来(并且在可能的情况下作为属性可读/可写)。
// @noErrors
const el = document.querySelector('my-element');
// 获取 'name' 属性的当前值
console.log(el.name);
// 设置新值,更新 shadow DOM
el.name = 'everybody';
注意,你需要明确列出所有属性,即在 组件选项 中没有声明 props
时使用 let props = $props()
意味着 Svelte 无法知道要在 DOM 元素上暴露哪些属性。
组件生命周期
自定义元素是使用包装器方法从 Svelte 组件创建的。这意味着内部 Svelte 组件并不知道它是一个自定义元素。自定义元素包装器负责适当地处理其生命周期。
当创建一个自定义元素时,它包裹的 Svelte 组件并不会立即创建。只有在调用 connectedCallback
后的下一个 tick 中创建。在将自定义元素插入 DOM 之前分配给它的属性会被临时保存,然后在组件创建时设置,因此它们的值不会丢失。
但是这对于调用自定义元素上导出的函数并不奏效,它们只有在元素挂载后才可用。如果你需要在组件创建之前调用函数,可以使用 extend
选项 选项来解决这个问题。
当使用 Svelte 编写的自定义元素被创建或更新时,shadow DOM 将在下一个 tick 反映该值,而不是立即反映。这种方式可以对更新进行批处理,并且临时(但同步地)将元素从 DOM 中分离的 DOM 移动不会导致内部组件被卸载。
内部 Svelte 组件会在调用 disconnectedCallback
后的下一个 tick 中被销毁。
组件选项
构造自定义元素时,自 Svelte 4 起,你可以通过在 <svelte:options>
中定义 customElement
为一个对象来定制多个方面。这个对象可以包含以下属性:
tag: string
:自定义元素名称的可选tag
属性。如果设置,则在导入此组件时,会在文档的customElements
注册表中定义具有此标记名称的自定义元素。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 表单集成,这会很有用。
<svelte:options
customElement={{
tag: 'custom-element',
shadow: 'none',
props: {
name: { reflect: true, type: 'Number', attribute: 'element-index' }
},
extend: (customElementConstructor) => {
// 扩展类,以便让它参与 HTML 表单
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// 在这里添加函数,而不是在下面的组件中
// 这样它始终可用,而不仅仅是在内部 Svelte 组件
// 挂载时才可用
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
<script>
let { elementIndex, attachedInternals } = $props();
// ...
function check() {
attachedInternals.checkValidity();
}
</script>
...
注意事项和限制
自定义元素可以作为一种有用的方式来打包组件以在非 Svelte 应用程序中使用,因为它们可以与原生 HTML 和 JavaScript 以及大多数框架一起工作。然而,有一些重要的差异需要注意:
- 样式是封装的,而不是仅仅 scoped 的(除非你设置
shadow: "none"
)。这意味着任何非组件样式(比如你在global.css
文件中的样式)都不会应用到自定义元素上,包括带有:global(...)
修饰符的样式 - 样式被内联到组件中作为 JavaScript 字符串,而不是被提取出来作为单独的 .css 文件。
- 自定义元素通常不适合服务端渲染,因为在 JavaScript 加载之前,shadow DOM 是不可见的
- 在 Svelte 中,插槽内容是延迟渲染的。在 DOM 中,它是立即渲染的。换句话说,即使组件的
<slot>
元素在{#if ...}
块内,它也会始终被创建。类似地,在{#each ...}
块中包含<slot>
不会导致插槽内容被多次渲染 - 已废弃的
let:
指令没有效果,因为自定义元素没有办法将数据传递给填充插槽的父组件 - 需要 polyfills 来支持较旧的浏览器
- 你可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能在自定义元素之间使用。换句话说,您不能在父自定义元素上使用
setContext
并在子自定义元素中用getContext
读取它。
Svelte 中文文档
点击查看《Svelte 自定义元素》介绍。
系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。
共同学习,写下你的评论
评论加载中...
作者其他优质文章