16. TypeScript в Qwik
Qwik создан с нуля с поддержкой TypeScript. Все API типизированы, а оптимизатор понимает TypeScript для правильной генерации QRL.
Типизация компонентов
Заголовок раздела «Типизация компонентов»import { component$ } from '@builder.io/qwik';
// Интерфейс для propsinterface 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> );});PropFunction — типизация колбэков
Заголовок раздела «PropFunction — типизация колбэков»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> );});useSignal с типами
Заголовок раздела «useSignal с типами»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');
// Nullableconst user = useSignal<User | null>(null);
// С useStoreinterface AppStore { user: User | null; cart: CartItem[]; theme: 'light' | 'dark'; settings: Record<string, unknown>;}
const store = useStore<AppStore>({ user: null, cart: [], theme: 'dark', settings: {},});Типизация routeLoader$
Заголовок раздела «Типизация routeLoader$»import { routeLoader$ } from '@builder.io/qwik-city';
// Тип автоматически выводится из returnexport 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 имеет тип Userconsole.log(user.value.email); // TypeScript знает тип!Типизация routeAction$
Заголовок раздела «Типизация routeAction$»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));Типизация contextId
Заголовок раздела «Типизация contextId»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>;});