Владение и жизненный цикл
Простыми словами
Всё, что компонент создал — таймеры, подписки, — убирается само, когда его кусок страницы исчезает. Здесь о том, как это работает, и о двух колбэках: onMount и onCleanup.
Всё, что компонент создаёт — подписки, таймеры, вложенные регионы, — регистрируется в дереве владения. Когда поддерево уходит, его ресурсы освобождаются каскадно, сначала дети. Это вся модель жизненного цикла: никаких фаз mount/update/unmount — только построить один раз и уничтожить один раз.
Откуда поддеревья берутся и куда деваются
mount(container, view)создаёт корневого владельца; возвращённая функция уничтожает всё.- Регион
dyn/Show/Dynamicуничтожает старое поддерево перед постройкой нового. - Строка
<Each>уничтожается, когда её ключ уходит из списка.
onCleanup
Регистрирует уборку в текущем владельце:
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:
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) — это процессы, и им нужен владелец. Внутри компонента скоуп есть автоматически; на уровне модуля создайте его сами:
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-дерева, которая всё равно должна умереть вместе с компонентом.
Незнакомый термин? Вся терминология этой документации объяснена в глоссарии.