Vanilla Disintegratev1.0.0
ZH
本页内容

响应式框架必须继续拥有它渲染的 DOM 节点。使用 detach 更新框架状态,而不是让 Vanilla Disintegrate 直接调用 element.remove()

effects.remove(element, {
  effect: 'dust',
  detach: () => removeFromFrameworkState(),
});

该钩子会在元素完成测量和截图后、正式提交删除时运行。若省略 detach,库仍会使用默认的 element.remove()

恢复时,先让框架渲染元素,等待 DOM 提交,再把新的元素引用传给 restore()。响应式状态应保存业务数据,而不是已分离的 DOM 节点,因此这类集成通常保持默认的 retain: false

由于恢复得到的是新的 DOM 节点,若删除时没有使用实例默认效果,请在 restore() 中显式传入相同的 effect。记忆的效果元数据只能跟随同一个被保留的 DOM 节点。

React

React 必须同步提交它所拥有节点的移除。使用 flushSync() 明确这条边界,并在 useLayoutEffect() 中恢复已经插入的新节点。

'use client';

import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { flushSync } from 'react-dom';
import Disintegrator from 'vanilla-disintegrate/snapdom';

export function DisintegratingCard() {
  const [visible, setVisible] = useState(true);
  const cardRef = useRef<HTMLElement | null>(null);
  const effectsRef = useRef<Disintegrator | null>(null);
  const pendingRestore = useRef(false);

  useEffect(() => {
    const effects = new Disintegrator();
    effectsRef.current = effects;
    return () => {
      effects.destroy();
      effectsRef.current = null;
    };
  }, []);

  useLayoutEffect(() => {
    const effects = effectsRef.current;
    const element = cardRef.current;
    if (!visible || !pendingRestore.current || !effects || !element) return;

    pendingRestore.current = false;
    effects.restore(element, { effect: 'dust' });
  }, [visible]);

  function removeCard() {
    const effects = effectsRef.current;
    const element = cardRef.current;
    if (!effects || !element) return;

    effects.remove(element, {
      effect: 'dust',
      detach: () => flushSync(() => setVisible(false)),
    });
  }

  function restoreCard() {
    pendingRestore.current = true;
    setVisible(true);
  }

  return visible ? (
    <article ref={cardRef}>
      <h2>动态</h2>
      <button onClick={removeCard}>删除</button>
    </article>
  ) : (
    <button onClick={restoreCard}>恢复</button>
  );
}

在 effect 中创建实例,并在 cleanup 中调用 destroy();该写法兼容 React Strict Mode。Next.js 等 React SSR 框架需要把组件标记为客户端组件。

Vue

Vue 会在浏览器绘制前批量应用状态更新。等待 nextTick() 后,restore() 就能获得最终 DOM 和几何信息。

<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import Disintegrator from 'vanilla-disintegrate/snapdom';

const visible = ref(true);
const card = ref<HTMLElement | null>(null);
let effects: Disintegrator | null = null;

onMounted(() => {
  effects = new Disintegrator();
});
onUnmounted(() => effects?.destroy());

function removeCard() {
  if (!effects || !card.value) return;
  effects.remove(card.value, {
    effect: 'dust',
    detach: () => {
      visible.value = false;
    },
  });
}

async function restoreCard() {
  visible.value = true;
  await nextTick();
  if (effects && card.value) effects.restore(card.value, { effect: 'dust' });
}
</script>

<template>
  <article v-if="visible" ref="card">
    <h2>动态</h2>
    <button @click="removeCard">删除</button>
  </article>
  <button v-else @click="restoreCard">恢复</button>
</template>

该组件同样适用于 Nuxt,因为浏览器专属初始化位于 onMounted() 中。

Svelte

通过 bind:this 获取实时节点,从 onMount() 返回清理函数,并在恢复前等待 tick()

<script lang="ts">
  import { onMount, tick } from 'svelte';
  import Disintegrator from 'vanilla-disintegrate/snapdom';

  let visible = true;
  let card: HTMLElement;
  let effects: Disintegrator | null = null;

  onMount(() => {
    effects = new Disintegrator();
    return () => effects?.destroy();
  });

  function removeCard() {
    if (!effects || !card) return;
    effects.remove(card, {
      effect: 'dust',
      detach: () => {
        visible = false;
      },
    });
  }

  async function restoreCard() {
    visible = true;
    await tick();
    if (effects && card) effects.restore(card, { effect: 'dust' });
  }
</script>

{#if visible}
  <article bind:this={card}>
    <h2>动态</h2>
    <button onclick={removeCard}>删除</button>
  </article>
{:else}
  <button onclick={restoreCard}>恢复</button>
{/if}

onMount() 只在浏览器执行,因此同样适用于 SvelteKit SSR。

Solid

Solid 会同步更新 signal 控制的分支。恢复时安排一个微任务,以确保新节点已插入。

import { createSignal, onCleanup, onMount, Show } from 'solid-js';
import Disintegrator from 'vanilla-disintegrate/snapdom';

export function DisintegratingCard() {
  const [visible, setVisible] = createSignal(true);
  let card!: HTMLElement;
  let effects: Disintegrator | null = null;

  onMount(() => {
    effects = new Disintegrator();
  });
  onCleanup(() => effects?.destroy());

  const removeCard = () =>
    effects?.remove(card, {
      effect: 'dust',
      detach: () => setVisible(false),
    });

  const restoreCard = () => {
    setVisible(true);
    queueMicrotask(() => effects?.restore(card, { effect: 'dust' }));
  };

  return (
    <Show when={visible()} fallback={<button onClick={restoreCard}>恢复</button>}>
      <article ref={card}>
        <h2>动态</h2>
        <button onClick={removeCard}>删除</button>
      </article>
    </Show>
  );
}

SolidStart 使用相同的客户端生命周期。

Angular

Angular Signals 可以在 detach 中提交条件视图。afterNextRender() 避免在 SSR 期间创建实例,DestroyRef 负责清理。

import {
  afterNextRender,
  ChangeDetectorRef,
  Component,
  DestroyRef,
  ElementRef,
  inject,
  signal,
  viewChild,
} from '@angular/core';
import Disintegrator from 'vanilla-disintegrate/snapdom';

@Component({
  selector: 'app-disintegrating-card',
  standalone: true,
  template: `
    @if (visible()) {
      <article #card>
        <h2>动态</h2>
        <button (click)="removeCard()">删除</button>
      </article>
    } @else {
      <button (click)="restoreCard()">恢复</button>
    }
  `,
})
export class DisintegratingCard {
  readonly visible = signal(true);
  readonly card = viewChild<ElementRef<HTMLElement>>('card');
  private readonly changeDetector = inject(ChangeDetectorRef);
  private readonly destroyRef = inject(DestroyRef);
  private effects: Disintegrator | null = null;

  constructor() {
    afterNextRender(() => (this.effects = new Disintegrator()));
    this.destroyRef.onDestroy(() => this.effects?.destroy());
  }

  removeCard() {
    const element = this.card()?.nativeElement;
    if (!this.effects || !element) return;
    this.effects.remove(element, {
      effect: 'dust',
      detach: () => {
        this.visible.set(false);
        this.changeDetector.detectChanges();
      },
    });
  }

  restoreCard() {
    this.visible.set(true);
    this.changeDetector.detectChanges();
    const element = this.card()?.nativeElement;
    if (this.effects && element) this.effects.restore(element, { effect: 'dust' });
  }
}

可用于任何 DOM 界面

Vanilla Disintegrate 可用于任何包含可测量 DOM 元素的浏览器界面。它不依赖框架,也不需要适配器或单独的集成包。

上面的 React、Vue、Svelte、Solid 和 Angular 并不是支持平台的清单,而是各自典型生命周期场景的示例。同样的方法适用于任何其他框架、库或纯 JavaScript 应用。

示例使用各框架的官方 DOM 和生命周期 API:React refs 与 effectsVue template refs 与 lifecycleSvelte lifecycleSolid refs 与 cleanupAngular component lifecycle

在 GitHub 上编辑此页