Skip to content

Владение и жизненный цикл

Простыми словами

Всё, что компонент создал — таймеры, подписки, — убирается само, когда его кусок страницы исчезает. Здесь о том, как это работает, и о двух колбэках: onMount и onCleanup.

Всё, что компонент создаёт — подписки, таймеры, вложенные регионы, — регистрируется в дереве владения. Когда поддерево уходит, его ресурсы освобождаются каскадно, сначала дети. Это вся модель жизненного цикла: никаких фаз mount/update/unmount — только построить один раз и уничтожить один раз.

Откуда поддеревья берутся и куда деваются

  • mount(container, view) создаёт корневого владельца; возвращённая функция уничтожает всё.
  • Регион dyn/Show/Dynamic уничтожает старое поддерево перед постройкой нового.
  • Строка <Each> уничтожается, когда её ключ уходит из списка.

onCleanup

Регистрирует уборку в текущем владельце:

tsx
import { onCleanup } from "@continuum-js/dom";

function Ticker() {
  const id = setInterval(poll, 5000);
  onCleanup(() => clearInterval(id));
  return <span>…</span>;
}

Что убирается само: привязки JSX ({b}, class={b}), обработчики (onClick), animationFrames(). Что требует явного onCleanup: голый e.listen(...) (передайте его отписку), события interval/delay (onCleanup(() => ticks.dispose())) и всё императивное, что вы завели сами.

onMount

Тело компонента выполняется до вставки его узлов в документ. Когда эффекту нужен живой элемент — фокус, измерения, сторонний виджет — регистрируйте его через onMount:

tsx
import { onMount } from "@continuum-js/dom";

function SearchBox() {
  const input = (<input />) as HTMLInputElement;
  onMount(() => input.focus());
  return input;
}

onMount срабатывает один раз, сразу после вставки узлов скоупа — при mount, при переключении dyn/Show, при новой строке Each. Дочерние скоупы монтируются раньше родительских. Поддерево, заменённое до того, как его вообще вставили, свой onMount не выполнит никогда.

Обе — обычные функции: зовите их в условиях, циклах и хелперах.

Состояние и эффекты принадлежат скоупу

Дерево владения — дом не только для таймеров. Правило Continuum: значения — формулы; состояние и эффекты принадлежат скоупу. Чистые производные (map, combine, filter) — рецепты: подключаются при первом слушателе, засыпают после последнего, sample() отвечает всегда — владелец им не нужен. Стейтовые производные (hold, accum, .on) и эффекты (perform) — это процессы, и им нужен владелец. Внутри компонента скоуп есть автоматически; на уровне модуля создайте его сами:

ts
import { root } from "@continuum-js/frp";

const count = root(() => clicks.accum(0, (_e, n) => n + 1));

Без владельца рантайм остановит вас обучающей ошибкой. Когда скоуп уничтожается, его hold/accum замирают на последнем значении: процесс отцепляется от источника, sample() продолжает отдавать последнее значение.

Корни и скоупы вручную

root(fn) создаёт самостоятельного владельца (им пользуется mount); scope(fn) — дочерний скоуп со своей ручкой уничтожения. Помимо модульного состояния (выше) они нужны, когда вы строите собственную машинерию — например, работу вне DOM-дерева, которая всё равно должна умереть вместе с компонентом.


Незнакомый термин? Вся терминология этой документации объяснена в глоссарии.