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

4. Qwik-компоненты

В Qwik компоненты создаются с помощью функции component$(). Символ $ — ключевой: он говорит оптимизатору Qwik, что данная функция должна быть вынесена в отдельный ленивый чанк.

import { component$ } from '@builder.io/qwik';
export const MyComponent = component$(() => {
return <div>Привет, Qwik!</div>;
});

Отличия от React:

  • component$() вместо обычной функции
  • Символ $ делает компонент лениво загружаемым
  • Компонент — это QRL (Qwik Resource Locator)

QRL — это URL-ссылка на фрагмент JavaScript. Вместо передачи функции напрямую, Qwik передаёт адрес функции.

// Без Qwik оптимизатора (концептуально):
const MyComponent = component$(
qrl('./chunk-abc.js', 'MyComponent_0BkQ6')
);
// С Qwik оптимизатором (что пишем мы):
export const MyComponent = component$(() => {
return <div>Hello</div>;
});
// Оптимизатор сам вынесет функцию в отдельный чанк
import { component$ } from '@builder.io/qwik';
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick$?: () => void; // Функции в props тоже QRL!
}
export const Button = component$<ButtonProps>(({ label, variant = 'primary', onClick$ }) => {
return (
<button
class={\`btn btn--\${variant}\`}
onClick$={onClick$}
>
{label}
</button>
);
});

Важно: функции в props должны быть QRL (с суффиксом $ в типе PropFunction<>):

import { component$, type PropFunction } from '@builder.io/qwik';
interface CardProps {
title: string;
onClose$: PropFunction<() => void>; // PropFunction для колбэков
}
export const Card = component$<CardProps>(({ title, onClose$ }) => {
return (
<div class="card">
<h2>{title}</h2>
<button onClick$={onClose$}></button>
</div>
);
});
import { component$, Slot } from '@builder.io/qwik';
// Компонент с children через <Slot />
export const Panel = component$<{ title: string }>(({ title }) => {
return (
<div class="panel">
<h3>{title}</h3>
<div class="panel-body">
<Slot /> {/* Здесь рендерятся children */}
</div>
</div>
);
});
// Использование:
export const App = component$(() => {
return (
<Panel title="Мой панель">
<p>Это контент внутри панели</p>
</Panel>
);
});
export const Layout = component$(() => {
return (
<div>
<header>
<Slot name="header" /> {/* Именованный слот */}
</header>
<main>
<Slot /> {/* Дефолтный слот */}
</main>
</div>
);
});
// Использование:
export const Page = component$(() => {
return (
<Layout>
<div q:slot="header">Навигация</div>
<p>Основной контент</p>
</Layout>
);
});

Для небольших компонентов без собственного состояния можно использовать component$ с встроенной функцией:

// Простой компонент без состояния
export const Badge = component$<{ text: string; color?: string }>(
({ text, color = '#ac7ef4' }) => (
<span style={{ background: color, padding: '2px 8px', borderRadius: 4 }}>
{text}
</span>
)
);

В отличие от React, у Qwik-компонентов минимальный жизненный цикл:

import { component$, useSignal, useTask$, useVisibleTask$ } from '@builder.io/qwik';
export const MyComponent = component$(() => {
const count = useSignal(0);
// Аналог useEffect — запускается на сервере и клиенте
useTask$(({ track }) => {
track(() => count.value);
console.log('count changed:', count.value);
});
// Запускается ТОЛЬКО в браузере (когда компонент виден)
useVisibleTask$(() => {
console.log('Component is visible in browser!');
return () => console.log('cleanup');
});
return <button onClick$={() => count.value++}>{count.value}</button>;
});