Перейти к содержимому
GitHub

Хуки жизненного цикла

В Svelte 5 жизненный цикл компонента состоит только из двух частей: его создания и уничтожения. Всё, что происходит между ними — например, обновление определённого состояния — не связано с компонентом в целом; уведомляются только те части, которые должны реагировать на изменение состояния. Это связано с тем, что под капотом наименьшая единица изменения — это не компонент, а (рендер) эффекты, которые компонент настраивает при инициализации. Следовательно, таких понятий, как хуки beforeUpdate / afterUpdate, не существует.

onMount

Функция onMount планирует выполнение колбэка сразу после того, как компонент будет смонтирован в DOM. Она должна вызываться во время инициализации компонента (но не обязательно находиться внутри компонента; она может быть вызвана из внешнего модуля).

onMount не выполняется внутри компонента, который рендерится на сервере.

<script>
  import { onMount } from 'svelte';

  onMount(() => {
    console.log('компонент смонтирован');
  });
</script>

Если из onMount возвращается функция, она будет вызвана при размонтировании компонента.

<script>
  import { onMount } from 'svelte';

  onMount(() => {
    const interval = setInterval(() => {
      console.log('beep');
    }, 1000);

    return () => clearInterval(interval);
  });
</script>

onDestroy

Планирует выполнение колбэка непосредственно перед размонтированием компонента.

Из onMount, beforeUpdate, afterUpdate и onDestroy это единственный хук, который выполняется внутри компонента на стороне сервера.

<script>
  import { onDestroy } from 'svelte';

  onDestroy(() => {
    console.log('компонент уничтожен');
  });
</script>

tick

Хотя хука afterUpdate не существует, вы можете использовать tick, чтобы убедиться, что пользовательский интерфейс обновлён перед продолжением. tick возвращает Promise, который выполняется после применения всех ожидающих изменений состояния или в следующей микрозадаче, если изменений нет.

<script>
  import { tick } from 'svelte';

  $effect.pre(() => {
    console.log('компонент собирается обновиться');
    tick().then(() => {
        console.log('компонент только что обновлён');
    });
  });
</script>

Устарело: beforeUpdate / afterUpdate

В Svelte 4 были хуки, которые выполнялись до и после обновления компонента в целом. Для обратной совместимости эти хуки были добавлены в Svelte 5, но они недоступны внутри компонентов, использующих руны.

<script>
  import { beforeUpdate, afterUpdate } from 'svelte';

  beforeUpdate(() => {
    console.log('компонент собирается обновиться');
  });

  afterUpdate(() => {
    console.log('компонент только что обновлён');
  });
</script>

Вместо beforeUpdate используйте $effect.pre, а вместо afterUpdate$effect. Эти руны предоставляют более детальный контроль и реагируют только на те изменения, которые вас действительно интересуют.

Пример окна чата

Чтобы реализовать окно чата, которое автоматически прокручивается вниз при появлении новых сообщений (но только если вы уже прокрутили окно вниз), нам нужно измерить DOM перед его обновлением.

В Svelte 4 мы делаем это с помощью beforeUpdate, но этот подход имеет недостатки — он срабатывает перед каждым обновлением, независимо от того, актуально оно или нет. В примере ниже нам нужно ввести проверки, такие как updatingMessages, чтобы убедиться, что мы не изменяем положение прокрутки при переключении тёмного режима.

С рунами мы можем использовать $effect.pre, который ведет себя так же, как $effect, но выполняется до обновления DOM. Если мы явно ссылаемся на messages внутри тела эффекта, он будет запускаться при каждом изменении messages, но не при изменении theme.

Таким образом, beforeUpdate и его не менее проблемный аналог afterUpdate устарели в Svelte 5.

<script>
-  import { beforeUpdate, afterUpdate, tick } from 'svelte';
+  import { tick } from 'svelte';

-  let updatingMessages = false;
-  let theme = 'dark';
-  let messages = [];
+  let theme = $state('dark');
+  let messages = $state([]);

  let viewport;

-  beforeUpdate(() => {
+  $effect.pre(() => {
-    if (!updatingMessages) return;
+    messages;
    const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;

    if (autoscroll) {
      tick().then(() => {
        viewport.scrollTo(0, viewport.scrollHeight);
      });
    }

-    updatingMessages = false;
  });

  function handleKeydown(event) {
    if (event.key === 'Enter') {
      const text = event.target.value;
      if (!text) return;

-      updatingMessages = true;
      messages = [...messages, text];
      event.target.value = '';
    }
  }

  function toggle() {
    toggleValue = !toggleValue;
  }
</script>

<div class:dark={theme === 'dark'}>
  <div bind:this={viewport}>
    {#each messages as message}
      <p>{message}</p>
    {/each}
  </div>

-  <input on:keydown={handleKeydown} />
+  <input onkeydown={handleKeydown} />

-  <button on:click={toggle}> Переключить тёмный режим </button>
+  <button onclick={toggle}> Переключить тёмный режим </button>
</div>