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

16. TypeScript в Qwik

Qwik создан с нуля с поддержкой TypeScript. Все API типизированы, а оптимизатор понимает TypeScript для правильной генерации QRL.

import { component$ } from '@builder.io/qwik';
// Интерфейс для props
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick$?: () => void; // QRL функция
}
// Компонент с типом
export const Button = component$<ButtonProps>(({
label,
variant = 'primary',
size = 'md',
disabled = false,
onClick$,
}) => {
return (
<button
class={\`btn btn--\${variant} btn--\${size}\`}
disabled={disabled}
onClick$={onClick$}
>
{label}
</button>
);
});
import { type PropFunction } from '@builder.io/qwik';
interface SearchProps {
placeholder?: string;
onSearch$: PropFunction<(query: string) => void>;
onClear$?: PropFunction<() => Promise<void>>;
}
export const Search = component$<SearchProps>(({ onSearch$, onClear$, placeholder }) => {
const query = useSignal('');
return (
<div>
<input
value={query.value}
onInput$={(_, el) => query.value = el.value}
placeholder={placeholder}
/>
<button onClick$={() => onSearch$(query.value)}>Найти</button>
{onClear$ && <button onClick$={onClear$}>Очистить</button>}
</div>
);
});
import { useSignal, useStore } from '@builder.io/qwik';
// Явный тип
const count = useSignal<number>(0);
const name = useSignal<string>('');
const isOpen = useSignal<boolean>(false);
// Union типы
type Status = 'idle' | 'loading' | 'success' | 'error';
const status = useSignal<Status>('idle');
// Nullable
const user = useSignal<User | null>(null);
// С useStore
interface AppStore {
user: User | null;
cart: CartItem[];
theme: 'light' | 'dark';
settings: Record<string, unknown>;
}
const store = useStore<AppStore>({
user: null,
cart: [],
theme: 'dark',
settings: {},
});
import { routeLoader$ } from '@builder.io/qwik-city';
// Тип автоматически выводится из return
export const useUser = routeLoader$(async ({ params }): Promise<User> => {
const user = await db.users.findUnique({ where: { id: params.id } });
if (!user) throw new Error('User not found');
return user;
});
// В компоненте
const user = useUser();
// user.value имеет тип User
console.log(user.value.email); // TypeScript знает тип!
import { routeAction$, zod$, z } from '@builder.io/qwik-city';
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
});
// Zod автоматически даёт тип
export const useCreatePost = routeAction$(
async (data) => {
// data: { title: string, content: string, published: boolean }
const post = await db.posts.create({ data });
return { post };
},
zod$(CreatePostSchema)
);
import { createContextId, useContextProvider, useContext } from '@builder.io/qwik';
import type { Signal } from '@builder.io/qwik';
// Типизированный контекст
const ThemeContext = createContextId<Signal<'light' | 'dark'>>('app.theme');
const UserContext = createContextId<User | null>('app.user');
// Провайдер
export const Root = component$(() => {
const theme = useSignal<'light' | 'dark'>('dark');
useContextProvider(ThemeContext, theme);
return <Slot />;
});
// Потребитель
export const ThemeButton = component$(() => {
const theme = useContext(ThemeContext);
// theme.value имеет тип 'light' | 'dark'
return <button onClick$={() => {
theme.value = theme.value === 'dark' ? 'light' : 'dark';
}}>Toggle</button>;
});