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

7. useTask и useVisibleTask

Побочные эффекты в Qwik управляются через useTask$() и useVisibleTask$(). Эти хуки заменяют useEffect из React, но работают принципиально иначе.

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>;
});

В отличие от React, зависимости в Qwik не объявляются в массиве — они отслеживаются явно через track():

useTask$(({ track }) => {
// Отслеживаем конкретное свойство
const name = track(() => state.name);
const age = track(() => state.age);
// Эффект запустится при изменении name ИЛИ age
console.log(\`\${name}, \${age} лет\`);
});
useTask$(() => {
// Выполняется один раз при монтировании
console.log('Компонент создан');
});

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$(() => { ... }, { strategy: 'intersection-observer' });
// Запустить при первом взаимодействии пользователя
useVisibleTask$(() => { ... }, { strategy: 'document-ready' });
// Запустить как только документ загружен
useVisibleTask$(() => { ... }, { strategy: 'document-idle' });

Некоторые объекты нельзя сериализовать (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
useEffect(() => {
// Только клиент
// Массив зависимостей — часто ошибки
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();
});