新鲜出炉!Svelte5 Attachment

158 阅读2分钟

Svelte5 Attachment

为什么弃用 Svelte4 中的 action?

语法合理化

● 旧语法 use:action = {params} 易产生歧义,参数传递歧义问题(看似平等关系,实为函数调用)

● 新语法 @attach = {fn} 明确表达“将函数附加到元素”的意图

● 支持内联函数:可直接写 @attach = {(node)=>{...}}

● 新语法与 @html 和 @debug 等指令形成视觉系统

响应式增强

● Actions 参数变化不会触发更新

● Attachments 自动跟踪依赖(基于 Runes)

扩展性提升

● 支持组件级附加(而不仅限于DOM 元素)

● 允许合并多个附加逻辑(旧语法需要手动合并动作)

● 为未来动画/过度系统预留统一接口

Svelte5 Attachments

Attachments 是元素挂在到 DOM 时运行的函数,他们可以返回一个函数,该函数在稍后 DOM 中删除元素时调用,此功能官网说需要在 Svelte 5.29 版本以上更新

基础案例

<script lang="ts">
	import type { Attachment } from 'svelte/attachments';


	const myAttachment: Attachment = (element) => {
		console.log(element.nodeName); // 'DIV'

		return () => {
			console.log('cleaning up');
		};
	};
  
  const navigatorAttach(){
		console.log('navigator')
	}
</script>

<div {@attach myAttachment} {@attach navigatorAttach}>...</div>

注意:一个元素可以附加多个 attach

传参案例

<script lang="ts">
	import tippy from 'tippy.js';  //一个tip的简单库直接 npm 安装就可以,npm install tippy.js
	import type { Attachment } from 'svelte/attachments';

	let content = $state('Hello!');

	function tooltip(content: string): Attachment {
		return (element) => {
			const tooltip = tippy(element, { content });
			return tooltip.destroy;
		};
	}
</script>

<input bind:value={content} />

<button {@attach tooltip(content)}>
	Hover me
</button>

注意:由于 tooltip 表达式在 $effect 中运行,因此每当 content 更改时, 附件将被销毁并重新创建

执行顺序:

	$effect(() => {
		console.log('调用effect', count);  //第一次 DOM 加载完成就执行,每次更新后,限制性return 内容
		return () => {
			console.log('destroy'); //DOM 加载完成不执行,下次 count 更改后先执行  
		};
	});

<button onclick={() => count++}>{count}</button>

● 第一次执行 $effect 中的内容,

● 再次更新后,先执行 return 中的内容,在执行 $effect 内容

内联案例

<canvas width=32 height=32 {@attach (canvas)=>{console.log(canvas)}}></canvas>

注意:由于是内联样式,无需传参,因为局部的变量可以直接使用

let config = { speed: 1.5 }; // 动态配置对象
<canvas width=32 height=32 {@attach (canvas)=>{console.log(canvas),console.log(config)}}></canvas>

将 attachments 传递给组件

当在组件上使用时,{@attach} 将创建一个Symbol。 如果组件将道具扩展到元素上,则该元素将接收这些 attach

//Button 
<script lang="ts">
	import type { HTMLButtonAttributes } from 'svelte/elements';

	let { children, ...props }: HTMLButtonAttributes = $props();
</script>

<!-- `props` includes attachments -->
<button {...props}>
	{@render children?.()}
</button>
<script>
	import tippy from 'tippy.js';
	import Button from './Button.svelte';

	let content = $state('Hello!');

	function tooltip(content) {
		return (node) => {
			const tooltip = tippy(node, { content });
			return tooltip.destroy;	
		};
	}
</script>

<input bind:value={content} />

<Button {@attach tooltip(content)}>
	Hover me
</Button>

已编程方式创建 attachments

创建一个对象键,当对象扩展到元素上时,使用 {@attach ...} 的编程替换方案。(createAttachmentKey() 的 Symbol 用于props扩散时的附件标识)

<script>
	import { createAttachmentKey } from 'svelte/attachments';

	const props = {
		class: 'cool',
		onclick: () => alert('clicked'),
		[createAttachmentKey()]: (node) => {
			node.textContent = 'attached!';
		}
	};
</script>

<button {...props}>click me</button>
function createAttachmentKey(): symbol;

内部原理

内部直接实现使用 effect

import { effect } from '../../reactivity/effects.js';

/**
 * @param {Element} node
 * @param {() => (node: Element) => void} get_fn
 */
export function attach(node, get_fn) {
	effect(() => {
		const fn = get_fn();

		// we use `&&` rather than `?.` so that things like
		// `{@attach DEV && something_dev_only()}` work
		return fn && fn(node);
	});
}