7. useTask и useVisibleTask
Побочные эффекты в Qwik управляются через useTask$() и useVisibleTask$(). Эти хуки заменяют useEffect из React, но работают принципиально иначе.
useTask$ — универсальный эффект
Заголовок раздела «useTask$ — универсальный эффект»useTask$ запускается как на сервере, так и на клиенте. Это аналог useEffect, но с возможностью серверного выполнения.
import { component$, useSignal, useTask$ } from '@builder.io/qwik';
export const Logger = component$(() => { const count = useSignal(0);
useTask$(({ track }) => { // track() подписывается на изменения const currentCount = track(() => count.value);
console.log('count изменился:', currentCount);
// Функция очистки (как return в useEffect) return () => { console.log('cleanup перед следующим запуском'); }; });
return <button onClick$={() => count.value++}>{count.value}</button>;});track() — отслеживание зависимостей
Заголовок раздела «track() — отслеживание зависимостей»В отличие от React, зависимости в Qwik не объявляются в массиве — они отслеживаются явно через track():
useTask$(({ track }) => { // Отслеживаем конкретное свойство const name = track(() => state.name); const age = track(() => state.age);
// Эффект запустится при изменении name ИЛИ age console.log(\`\${name}, \${age} лет\`);});Эффект без track (один раз)
Заголовок раздела «Эффект без track (один раз)»useTask$(() => { // Выполняется один раз при монтировании console.log('Компонент создан');});useVisibleTask$ — только браузер
Заголовок раздела «useVisibleTask$ — только браузер»useVisibleTask$ запускается только в браузере и только когда компонент виден в viewport:
import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
export const Timer = component$(() => { const seconds = useSignal(0);
useVisibleTask$(() => { // Это НЕ выполняется на сервере! const interval = setInterval(() => { seconds.value++; }, 1000);
// Cleanup при уничтожении компонента return () => clearInterval(interval); });
return <div>Прошло: {seconds.value} сек</div>;});Стратегии запуска useVisibleTask$
Заголовок раздела «Стратегии запуска useVisibleTask$»// Запустить сразу при видимости (по умолчанию)useVisibleTask$(() => { ... }, { strategy: 'intersection-observer' });
// Запустить при первом взаимодействии пользователяuseVisibleTask$(() => { ... }, { strategy: 'document-ready' });
// Запустить как только документ загруженuseVisibleTask$(() => { ... }, { strategy: 'document-idle' });noSerialize — браузерные объекты
Заголовок раздела «noSerialize — браузерные объекты»Некоторые объекты нельзя сериализовать (WebSocket, Map, интервалы). Для них используется noSerialize:
import { component$, useStore, useVisibleTask$, noSerialize } from '@builder.io/qwik';
export const WebSocketDemo = component$(() => { const state = useStore<{ ws: WebSocket | undefined; messages: string[]; }>({ ws: undefined, // WebSocket не сериализуем! messages: [], });
useVisibleTask$(() => { // Оборачиваем в noSerialize state.ws = noSerialize(new WebSocket('wss://api.example.com'));
state.ws!.onmessage = (event) => { state.messages.push(event.data); };
return () => state.ws?.close(); });
return ( <ul> {state.messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );});Сравнение с React useEffect
Заголовок раздела «Сравнение с React useEffect»// React useEffectuseEffect(() => { // Только клиент // Массив зависимостей — часто ошибки fetchData(userId); return () => cleanup();}, [userId]); // ← Зависимости декларативно
// Qwik useTask$useTask$(({ track }) => { // Сервер + клиент // Зависимости через track() — явно const id = track(() => userId.value); fetchData(id); return () => cleanup();}); // ← Зависимостей нет, они в track()
// Qwik useVisibleTask$ (только клиент)useVisibleTask$(() => { // Только браузер, только при видимости initBrowserAPI(); return () => cleanup();});