9. Lifecycle: onMount, onCleanup
Solid принципиально отличается от React отсутствием «жизненного цикла компонента». Компоненты в Solid — это просто функции, которые вызываются один раз. Вся реактивность — через примитивы: createEffect, onMount, onCleanup.
Нет componentDidUpdate — всё через эффекты
Заголовок раздела «Нет componentDidUpdate — всё через эффекты»В React есть методы жизненного цикла: componentDidMount, componentDidUpdate, componentWillUnmount. В Solid их нет. Вместо них — три примитива:
import { createEffect, onMount, onCleanup } from 'solid-js';
function MyComponent() { // 1. onMount — аналог componentDidMount, запускается ОДИН РАЗ onMount(() => { console.log('DOM смонтирован'); });
// 2. createEffect — аналог useEffect, запускается при изменении зависимостей createEffect(() => { console.log('Реактивное обновление'); });
// 3. onCleanup — аналог componentWillUnmount (или cleanup в useEffect) onCleanup(() => { console.log('Компонент удалён из DOM'); });
return <div>Компонент</div>;}onMount — однократное выполнение
Заголовок раздела «onMount — однократное выполнение»onMount — это просто createEffect без зависимостей, который запускается после вставки в DOM:
import { onMount, createSignal } from 'solid-js';
function Canvas() { let canvasRef: HTMLCanvasElement; const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
onMount(() => { // DOM гарантированно доступен const context = canvasRef.getContext('2d')!; setCtx(context);
// Начальная отрисовка context.fillStyle = '#2c67d5'; context.fillRect(0, 0, 300, 150); });
return <canvas ref={canvasRef!} width={300} height={150} />;}Важно: onMount синхронен. Если нужна асинхронная загрузка — используй createResource или createEffect с async IIFE.
onCleanup — очистка ресурсов
Заголовок раздела «onCleanup — очистка ресурсов»onCleanup регистрирует функцию очистки, которая запускается когда:
- Компонент удаляется из DOM
- Эффект повторно запускается (между запусками)
- Владелец (owner) уничтожается
import { createEffect, onCleanup, createSignal } from 'solid-js';
function Timer() { const [seconds, setSeconds] = createSignal(0);
onMount(() => { // Запускаем таймер при монтировании const id = setInterval(() => setSeconds(s => s + 1), 1000);
// onCleanup внутри onMount — очищаем при демонтировании onCleanup(() => clearInterval(id)); });
return <div>Прошло: {seconds()} сек</div>;}
// onCleanup внутри createEffect — запускается перед каждым повторным эффектомfunction Subscription(props: { channel: string }) { createEffect(() => { const channel = props.channel; // Зависимость const ws = new WebSocket(\`wss://api.example.com/\${channel}\`);
onCleanup(() => { ws.close(); // Закрываем соединение перед переключением канала }); });
return <div>Слушаем: {props.channel}</div>;}createEffect — реактивные побочные эффекты
Заголовок раздела «createEffect — реактивные побочные эффекты»createEffect автоматически отслеживает сигналы, к которым обращается:
import { createSignal, createEffect } from 'solid-js';
function SearchBox() { const [query, setQuery] = createSignal(''); const [results, setResults] = createSignal<string[]>([]);
createEffect(() => { const q = query(); // Регистрируем зависимость от query
if (q.length < 2) { setResults([]); return; }
// Этот код запустится при каждом изменении query const abortController = new AbortController();
fetch(\`/api/search?q=\${q}\`, { signal: abortController.signal }) .then(r => r.json()) .then(setResults) .catch(() => {}); // Игнорируем ошибку отмены
// Очистка — отменяем предыдущий запрос при новом вводе onCleanup(() => abortController.abort()); });
return ( <div> <input value={query()} onInput={e => setQuery(e.target.value)} /> <For each={results()}>{r => <div>{r}</div>}</For> </div> );}Порядок выполнения
Заголовок раздела «Порядок выполнения»1. Функция компонента вызывается (синхронно) ↓2. JSX рендерится, создаётся дерево DOM ↓3. onMount колбэки запускаются (bottom-up: дочерние раньше родительских) ↓4. createEffect запускается (синхронно после mount) ↓Изменение сигнала →5. Затронутые эффекты перезапускаются: a. Запускается onCleanup предыдущего запуска b. Запускается тело эффекта заново ↓Компонент удаляется →6. onCleanup для эффектов7. onCleanup для onMountДерево владельцев (Owner Tree)
Заголовок раздела «Дерево владельцев (Owner Tree)»Solid строит дерево «владельцев» — каждый createEffect, createMemo, createRoot создаёт контекст владельца. onCleanup регистрируется у текущего владельца:
import { createRoot, createSignal, onCleanup, getOwner } from 'solid-js';
// createRoot — создать изолированный контекст реактивностиconst dispose = createRoot(cleanup => { const [count, setCount] = createSignal(0);
createEffect(() => { console.log('count:', count()); onCleanup(() => console.log('эффект очищен')); });
// cleanup == dispose функция для всего root return cleanup;});
// Уничтожить все эффекты и очистить подпискиdispose();Асинхронные операции в эффектах
Заголовок раздела «Асинхронные операции в эффектах»// ❌ НЕ делай эффекты asynccreateEffect(async () => { const data = await fetch('/api/data'); // Зависимости, читаемые ПОСЛЕ await, не отслеживаются! setData(await data.json());});
// ✅ Читай зависимости ДО async операцииcreateEffect(() => { const id = props.id; // ← Зависимость читается синхронно, отслеживается
const controller = new AbortController(); fetch(\`/api/data/\${id}\`, { signal: controller.signal }) .then(r => r.json()) .then(setData);
onCleanup(() => controller.abort());});
// ✅ Или используй createResource для async данныхconst [data] = createResource(() => props.id, fetchData);