はじめに
Reactやvueで作ったサイトが、表示は速いのにボタンを押してもしばらく反応しない――そんな経験はないでしょうか。これは「ハイドレーション」と呼ばれる処理が原因です。サーバーで描画したHTMLに対して、ブラウザ側でもう一度JavaScriptを実行し、イベントハンドラを一つずつ結びつけ直す作業が必要になるためです。ページが大きくなるほどこの処理は重くなり、体感速度を悪化させます。
Qwikはこの「ハイドレーション」という工程そのものをなくすという、思い切ったアプローチを取るフレームワークです。今回はQwikの考え方と、実際にコードを動かしながらその特徴を見ていきます。
Qwikとは
QwikはBuilder.io社が開発しているWebフレームワークで、「resumability(再開可能性)」という独自の概念によって、努力なしで瞬時に読み込まれるWebアプリを目指しています。JSXや関数コンポーネントなどReactに近い書き方を採用しているため、既存のReact経験者であれば学習コストを抑えて始められます。
主な特徴
- Resumability(再開可能性) - サーバーでレンダリングした状態をそのままシリアライズしてHTMLに埋め込み、ブラウザ側では状態の再構築(ハイドレーション)を行わずに「実行を再開」します
- 細粒度の遅延ロード - コンポーネント単位ではなく、イベントハンドラや副作用単位で自動的にコードを分割し、実際にユーザーが操作した瞬間にだけ該当するJSを読み込みます
- Batteries included - Vite、Vitest、Playwrightなどが標準で組み込まれており、
npm create qwik@latestだけで開発を始められます - Qwik City - ファイルベースルーティングやSSR/SSGを備えたフルスタックフレームワークが同梱されています
インストール
新規プロジェクトを作る場合は、CLIから対話形式でセットアップします。
npm create qwik@latest
既存プロジェクトにコアライブラリだけを追加する場合は以下の通りです。
npm install @builder.io/qwik
Qwikの基本的な使い方をサンプルで確認する
Qwikのコンポーネントは component$ で定義します。末尾の $ は「ここが遅延ロードの境界になる」ことを示すQwik独自の記法で、Qwikオプティマイザがこの印を目印にコードを自動分割します。状態管理には useSignal を使い、count.value のように値を読み書きします。
以下はカウンターの実装例です。component$ でコンポーネントを定義し、useSignal で作った count の .value を書き換えるとその箇所だけ再描画されます。中心となる部分だけを抜き出すと次のようになります。
import { component$, useSignal } from '@builder.io/qwik@1.20.0'
const Counter = component$(() => {
const count = useSignal(0)
return (
<div>
<p>カウント: {count.value}</p>
<button onClick$={() => count.value++}>+1する</button>
</div>
)
})
ブラウザ上でそのまま編集・実行できるのが以下のサンプルです。
onClick$ のようにイベント名にも $ が付いているのが特徴です。このハンドラの中身は、実際にクリックされるまでブラウザにダウンロードされません。今回はesm.sh経由でその場でモジュールを読み込む都合上、通常のアプリで得られる「初期表示時のJS量ゼロ」までは再現できませんが、書き味そのものは本番と同じです。count.value++ の部分を count.value += 10 のように書き換えれば、クリック1回あたりの増分を自由に変えられます。useSignal の初期値を useSignal(100) にすれば、カウントの開始値を変更できます。
実践的なユースケース
QwikのuseStoreでオブジェクトの状態をまとめて管理する
useSignal は単一の値の管理に向いていますが、複数のプロパティを持つオブジェクトや配列をまとめて扱いたい場合は useStore を使います。プロパティの一部だけを更新しても、Qwikが変更を検知して該当箇所だけを再描画します。
以下はTODOリストの例です。入力した文字列を配列に追加していきます。useStore で作ったオブジェクトのプロパティは、通常のオブジェクトと同じように読み書きできる点がポイントです。要点だけを抜き出すと次のようになります。
import { component$, useStore } from '@builder.io/qwik@1.20.0'
const TodoApp = component$(() => {
const store = useStore({
text: '',
todos: ['Qwikの基本を学ぶ'],
})
return (
<div>
<input
value={store.text}
onInput$={(_, el) => (store.text = el.value)}
/>
<button onClick$={() => store.todos.push(store.text)}>
追加
</button>
</div>
)
})
実際に動かして確かめられるのが以下のサンプルです。
store.todos.push(...) のように配列のメソッドを呼ぶだけで、Qwikが変更を検知して <ul> の再描画まで面倒を見てくれます。初期値の todos を空配列 [] にすれば空の状態から試せますし、store.todos.filter((_, i) => i !== index) のような処理を追加すれば削除ボタンも実装できます。
QwikのuseTask$で値の変化を追跡して副作用を実行する
複数のSignalやStoreの値から別の値を導出したい場合は useTask$ を使います。track に渡した値が変化するたびに、コールバックが再実行されます。サーバー・クライアントどちらでも実行できるため、初期表示時の計算にも使えます。
以下は単価と数量から合計金額を自動計算する例です。useTask$ に渡したコールバックの中で track を呼ぶと、その値が変化するたびに再計算が走ります。要点だけを抜き出すと次のようになります。
import { component$, useSignal, useTask$ } from '@builder.io/qwik@1.20.0'
const PriceCalculator = component$(() => {
const price = useSignal(1000)
const quantity = useSignal(1)
const total = useSignal(0)
useTask$(({ track }) => {
track(() => price.value)
track(() => quantity.value)
total.value = price.value * quantity.value
})
return <p>合計: {total.value}円</p>
})
ブラウザ上で単価・数量を書き換えて挙動を確認できるのが以下のサンプルです。
track に渡すコールバックの数を増やせば、監視対象はいくつでも追加できます。例えば送料計算用の shipping というSignalをもう一つ用意して track(() => shipping.value) を足せば、total.value の計算式に組み込むだけで合計金額の自動再計算に反映されます。
QwikのuseVisibleTask$でブラウザ表示後にだけ処理を実行する
setInterval やDOM操作など、ブラウザ環境でしか実行できない処理には useVisibleTask$ を使います。名前の通り、コンポーネントが実際に画面に表示されたタイミングで初めて実行されるため、サーバーサイドレンダリングと共存させても安全です。
以下は1秒ごとに現在時刻を更新する時計の例です。cleanup に登録した処理は、コンポーネントが破棄されるタイミングで自動的に呼び出されます。要点だけを抜き出すと次のようになります。
import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik@1.20.0'
const Clock = component$(() => {
const time = useSignal('起動中...')
useVisibleTask$(({ cleanup }) => {
const id = setInterval(() => {
time.value = new Date().toLocaleTimeString('ja-JP')
}, 1000)
cleanup(() => clearInterval(id))
})
return <p>現在時刻: {time.value}</p>
})
実際にブラウザ上で時刻が更新される様子を確認できるのが以下のサンプルです。
setInterval の間隔である 1000(ミリ秒)を 100 に変えると、より短い周期で時刻が更新される様子を確認できます。useVisibleTask$ はこのように副作用の開始処理と、cleanup による後片付けをセットで書けるのが特徴です。
まとめ
Qwikは「ハイドレーションをなくす」という一点に振り切ることで、大規模なWebアプリでも初期表示のインタラクティブ性を犠牲にしないという明確な答えを出したフレームワークです。component$ や onClick$ といった $ 記法は最初こそ独特に感じますが、これはQwikオプティマイザがコードを自動分割するための目印であり、書き味自体はReactに近く違和感なく書けます。
useSignal によるシンプルな状態管理から、useStore でのオブジェクト管理、useTask$ や useVisibleTask$ による副作用の制御まで一通り体験してみると、Reactの useState や useEffect に慣れている方ほどスムーズに移行できるはずです。実際のプロジェクトでは、Qwik Cityを使ったファイルベースルーティングやSSR/SSGの機能も揃っているので、まずは npm create qwik@latest で公式のスターターを触ってみることをおすすめします。