6. useSignal и useStore
Реактивное состояние в Qwik основано на двух примитивах: useSignal() для простых значений и useStore() для сложных объектов. Они работают принципиально иначе, чем useState в React.
useSignal — реактивный примитив
Заголовок раздела «useSignal — реактивный примитив»useSignal создаёт реактивный контейнер для одного значения:
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => { const count = useSignal(0); // Начальное значение
return ( <div> <p>Счётчик: {count.value}</p> {/* Доступ через .value */} <button onClick$={() => count.value++}>+</button> <button onClick$={() => count.value--}>-</button> </div> );});Ключевое отличие от React: в React setState заменяет значение и вызывает ре-рендер всего компонента. В Qwik изменение .value точечно обновляет только те DOM-узлы, которые используют это значение.
useStore — реактивный объект
Заголовок раздела «useStore — реактивный объект»useStore создаёт реактивный объект — аналог useReducer или глубокого useState:
import { component$, useStore } from '@builder.io/qwik';
interface AppState { user: string; count: number; items: string[];}
export const App = component$(() => { const state = useStore<AppState>({ user: 'Алекс', count: 0, items: [], });
return ( <div> <p>Привет, {state.user}!</p> <p>Счётчик: {state.count}</p> <button onClick$={() => { state.count++; state.items.push(\`Item \${state.count}\`); }}> Добавить </button> </div> );});Глубокая реактивность
Заголовок раздела «Глубокая реактивность»const nested = useStore({ user: { profile: { name: 'Алекс', age: 25 } }});
// Изменение вложенных свойств — всё реактивно!nested.user.profile.name = 'Борис';nested.user.profile.age++;Сравнение useSignal vs useStore
Заголовок раздела «Сравнение useSignal vs useStore»| Характеристика | useSignal | useStore |
|---|---|---|
| Тип данных | Примитивы, объекты | Объекты, массивы |
| Доступ | .value | Напрямую |
| Сериализация | ✅ Автоматически | ✅ Автоматически |
| Глубокая реактивность | ❌ | ✅ |
| Производительность | Максимальная | Высокая |
Вычисляемые значения
Заголовок раздела «Вычисляемые значения»В Qwik нет встроенного useMemo как в React — вместо этого можно использовать useComputed$:
import { component$, useSignal, useComputed$ } from '@builder.io/qwik';
export const PriceCalc = component$(() => { const price = useSignal(100); const quantity = useSignal(3);
// Вычисляемый сигнал const total = useComputed$(() => price.value * quantity.value);
return ( <div> <p>Цена: {price.value}</p> <p>Количество: {quantity.value}</p> <p>Итого: {total.value}</p> </div> );});Ссылки на DOM-элементы
Заголовок раздела «Ссылки на DOM-элементы»import { component$, useSignal } from '@builder.io/qwik';
export const FocusInput = component$(() => { const inputRef = useSignal<HTMLInputElement>();
return ( <> <input ref={inputRef} type="text" placeholder="Введи текст..." /> <button onClick$={() => inputRef.value?.focus()}> Фокус </button> </> );});Передача сигналов между компонентами
Заголовок раздела «Передача сигналов между компонентами»// Родительский компонент создаёт сигналexport const Parent = component$(() => { const theme = useSignal<'light' | 'dark'>('dark');
return ( <div> <ThemeToggle theme={theme} /> <Content theme={theme} /> </div> );});
// Дочерний компонент получает сигнал как propexport const ThemeToggle = component$<{ theme: Signal<string> }>(({ theme }) => { return ( <button onClick$={() => { theme.value = theme.value === 'dark' ? 'light' : 'dark'; }}> Тема: {theme.value} </button> );});useContext — глобальное состояние
Заголовок раздела «useContext — глобальное состояние»import { createContextId, useContext, useContextProvider } from '@builder.io/qwik';
// Создаём контекстconst ThemeContext = createContextId<Signal<string>>('theme');
// Провайдерexport const Root = component$(() => { const theme = useSignal('dark'); useContextProvider(ThemeContext, theme); return <App />;});
// Потребительexport const Button = component$(() => { const theme = useContext(ThemeContext); return <button class={theme.value}>Click</button>;});