Mid*_*run 6 svelte svelte-component svelte-3
我正在开发一个以图形方式呈现树结构的应用程序。树中有各种节点类型,每种类型都有一个相应的 Svelte 组件来渲染它。
该应用程序的功能之一是您可以单击并拖动树中的节点,并将其移动到不同的父节点。当将节点放到新位置时,底层数据结构会相应地转换,并且 Svelte 会更新视图。
问题是,被移动节点的组件实例被破坏(连同它的所有后代,可能是数百个节点),然后无论节点移动到哪里,整个事物都会从头开始重新创建。这是很多不必要的工作,并且有非常明显的滞后,而它本应该能够立即移动现有的组件实例和关联的 DOM 节点。
有什么方法可以提示 Svelte 应该重用该实例吗?
(每个节点上都有一个唯一的 ID,如果有帮助的话。)
您无法向 Svelte 暗示它将重用该实例,但您可以创建一个借用系统,将组件安全地移动到其他容器。我已将其打包到一个库中,svelte-reparent.
通过制作原始容器Limbo和渲染器 ,Portals并且能够teleport在这些组件之间进行操作,我们还可以:
Limbo(安全地允许返回空间)Portal安全地将其移回Limbo由于 Svelte 没有虚拟 DOM,因此移动元素要容易得多:
Limbo.svelte- 我们的容器
<!--
Limbo is a place to initialize the element,
and serves as a hidden space to keep track of nodes.
Since Limbo owns the lifecycle of the current element,
the moment that Limbo gets destroyed, it will destroy its child element
that it *thinks* it owns. Because of this, it is quite safe to destroy and reinitialize
a Limbo component without causing unintended side effects to the DOM.
-->
<script lang="ts">
import { _components } from '$lib/Portal.svelte';
import { onMount } from 'svelte';
export let component: HTMLElement;
let container: HTMLDivElement;
// Register the component and its limbo
onMount(() => _components.set(component, { ..._components.get(component), limbo: container }));
</script>
<!--
We don't want to render this component,
but we use it as the initial holder before teleporting it.
This allows us to have a safe fallback
for when a Portal gets destroyed.
We also wrap it to guarantee that `component` is a DOM component,
since we can't guarantee that all svelte components only have 1 root node.
-->
<div style="display: none;" bind:this={container}>
<div style="display: contents;" bind:this={component}><slot /></div>
</div>
Run Code Online (Sandbox Code Playgroud)
Portal.svelte
<script context="module" lang="ts">
import { writable } from 'svelte/store';
type Container = HTMLElement;
type Key = string | number | symbol;
/**
* Universal map to keep track of what portal a component wants to be in,
* as well as its original limbo owner.
*
* DON'T MODIFY EXTERNALLY!
* Doing so is **undefined behavior**.
*/
export let _components = new Map<
Container,
{
limbo?: HTMLElement;
key?: Key;
}
>();
// dirty tracker - a Map isn't reactive, so we need to coerce Svelte to re-render
let dirty = writable(Symbol());
export async function teleport(component: Container, key: Key) {
_components.set(component, { ..._components.get(component), key });
// trigger a re-render
dirty.set(Symbol());
}
</script>
<script lang="ts">
import { onDestroy } from 'svelte';
export let key: Key;
export let component: Container;
/*
- component may be nil before mount
- listen to dirty to force a re-render
*/
$: if (component && $dirty && _components.get(component)?.key == key) {
// appendChild forces a move, not a copy - we can safely use this as the DOM
// handles ownership of the node for us
container.appendChild(component);
}
let container: HTMLDivElement;
onDestroy(() => {
// check if we own the component
const { limbo, key: localKey } = _components.get(component) || {};
if (localKey !== key) return;
_components.delete(component);
// move the component back to the limbo till it gets re-mounted
limbo?.appendChild(component);
// trigger a re-render
dirty.set(Symbol());
});
</script>
<div style="display: contents;" bind:this={container} />
Run Code Online (Sandbox Code Playgroud)
+page.svelte (example):
<script lang="ts">
import { onMount } from 'svelte';
import { Portal, Limbo, teleport } from '$lib';
let component: HTMLElement;
function send(label: string) {
return () => {
teleport(component, label);
};
}
onMount((): void => send('a')());
</script>
<main>
<Limbo bind:component>
<input placeholder="Enter unkept state" />
</Limbo>
<div class="container">
<h1>Container A</h1>
<Portal key="a" {component} />
<button on:click={send('a')}>Move Component Here</button>
</div>
<div class="container">
<h1>Container B</h1>
<Portal key="b" {component} />
<button on:click={send('b')}>Move Component Here</button>
</div>
</main>
<style>
.container {
border: 1px solid black;
margin: 1rem;
padding: 1rem;
}
</style>
Run Code Online (Sandbox Code Playgroud)