すぐに Qwik を始める
Qwik は新しいタイプのフレームワークで、再開可能(積極的な JS 実行とハイドレーションなし)、エッジ向けに構築されており、React 開発者にも馴染みやすいです。
すぐに試してみるには、Qwik のブラウザ内プレイグラウンドをご覧ください。
- StackBlitz Qwik(フル Qwik + Qwikcity 統合)
- サンプルプレイグラウンド(Qwik のみ、ルーティングなし)
前提条件
ローカルで Qwik を始めるには、次のものが必要です。
- Node.js v18.17 以降
- お好みの IDE(vscode を推奨)
- オプションで、Qwik を考える を読んでください。
CLI を使用したアプリの作成
まず、Qwik CLI を使用して空のスターターアプリケーションを生成し、すぐに使い慣れてください。同じコマンドを使用して、Qwik または Qwik city のプロジェクトを作成できます。
シェルで Qwik CLI を実行します。Qwik は npm、yarn、pnpm、bun をサポートしています。お好みのパッケージマネージャーを選択し、次のコマンドのいずれかを実行してください。
npm create qwik@latest
pnpm create qwik@latest
yarn create qwik
bun create qwik@latest
CLI は、プロジェクト名を設定し、スターターのいずれかを選択し、依存関係をインストールするかどうかを確認するインタラクティブなメニューをガイドします。生成されたファイルの詳細については、プロジェクト構造 のドキュメントを参照してください。
開発サーバーを起動します。
npm start
pnpm start
yarn start
bun start (on windows: bun run start)
Qwik ジョークアプリ
Qwik Hello World チュートリアルでは、Qwik でジョークアプリを構築しながら、最も重要な Qwik の概念を説明します。https://icanhazdadjoke.com からランダムなジョークを表示し、クリックして新しいジョークを取得するボタンを備えています。
1. ルートの作成
特定のルートでページを提供することから始めます。この基本的なアプリは、` /joke/` ルートでランダムな dad joke アプリケーションを提供します。このチュートリアルは、ディレクトリベースの ルーティングを使用する Qwik のメタフレームワークである Qwikcity に依存しています。開始するには
- プロジェクトで、`routes` に新しい `joke` ディレクトリを作成し、その中に `index.tsx` ファイルを作成します。
- 各ルートの `index.tsx` ファイルには `export default component$(...)` が必要です。これにより、Qwikcity は提供するコンテンツを認識します。次のコンテンツを `src/routes/joke/index.tsx` に貼り付けます。
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <section class="section bright">A Joke!</section>;
});
- `localhost:5173/joke/` に移動して、新しいページが機能していることを確認します。
注記
- `joke` ルートのデフォルトコンポーネントは、既存のレイアウトで囲まれています。レイアウト で、レイアウトとは何か、使用方法の詳細を確認してください。
- routes フォルダー内の index.tsx、layout.tsx、root.tsx、およびすべてのエントリファイルには **export default** が必要です。その他のコンポーネントでは、export const と export function を使用できます。
- コンポーネントの作成方法の詳細については、コンポーネント API セクションを参照してください。
2. データの読み込み
ランダムなジョークを読み込むために、https://icanhazdadjoke.com の外部JSON APIを使用します。ルートローダー を使用して、このデータをサーバーに読み込み、コンポーネントでレンダリングできます。
src/routes/joke/index.tsx
を開き、以下のコードを追加します。
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export default component$(() => {
// Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
const dadJokeSignal = useDadJoke();
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
</section>
);
});
- これで、
http://localhost:5173/joke/
で、ブラウザにランダムなジョークが表示されます。
コードの説明
routeLoader$
に渡される関数は、コンポーネントがレンダリングされる前にサーバー上でeagerly(即座に)呼び出され、データの読み込みを担当します。routeLoader$
は、サーバーデータを取得するためにコンポーネントで使用できるuse-hook、useDadJoke()
を返します。
注記
routeLoader$
は、そのuse-hookがコンポーネントで呼び出されなくても、コンポーネントがレンダリングされる前にサーバー上でeagerly(即座に)呼び出されます。routeLoader$
の戻り値の型は、追加の型情報なしでコンポーネント内で推論されます。
3. サーバーへのデータの投稿
これまで、コンポーネントrouteLoader$
はサーバーからクライアントにデータを送信するために使用されていました。クライアントからサーバーにデータを送信(投稿)するには、routeAction$
を使用します。
注:routeAction$
は、ブラウザのネイティブフォームAPIを使用するため、JavaScriptが無効になっている場合でも動作するため、サーバーにデータを送信する推奨方法です。
アクションを宣言するには、以下のコードを追加します。
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
export const useJokeVoteAction = routeAction$((props) => {
// Leave it as an exercise for the reader to implement this.
console.log('VOTE', props);
});
<Form>
を使用してuseJokeVoteAction
フックを使用するように、export default
コンポーネントを更新します。
export default component$(() => {
const dadJokeSignal = useDadJoke();
const favoriteJokeAction = useJokeVoteAction();
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
<Form action={favoriteJokeAction}>
<input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
<button name="vote" value="up">👍</button>
<button name="vote" value="down">👎</button>
</Form>
</section>
);
});
- これで、
http://localhost:5173/joke/
にボタンが表示され、クリックするとその値がコンソールに出力されます。
コードの説明
routeAction$
がデータを受け取ります。routeAction$
に渡される関数は、フォームが投稿されるたびにサーバー上で呼び出されます。routeAction$
は、フォームデータを送信するためにコンポーネントで使用できるuse-hook、useJokeVoteAction
を返します。
Form
は、ブラウザのネイティブな<form>
要素をラップする便利なコンポーネントです。
注意事項
- 検証については、zod検証 を参照してください。
routeAction$
は、JavaScriptが無効になっている場合でも動作します。- JavaScriptが有効になっている場合、
Form
コンポーネントはブラウザによるフォームの投稿を阻止し、代わりにJavaScriptを使用してデータを送信し、完全なリフレッシュなしでブラウザのネイティブフォームの動作をエミュレートします。
参考までに、このセクションの完全なコードスニペットを以下に示します。
import { component$ } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export const useJokeVoteAction = routeAction$((props) => {
console.log('VOTE', props);
});
export default component$(() => {
// Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
const dadJokeSignal = useDadJoke();
const favoriteJokeAction = useJokeVoteAction();
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
<Form action={favoriteJokeAction}>
<input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
<button name="vote" value="up">
👍
</button>
<button name="vote" value="down">
👎
</button>
</Form>
</section>
);
});
4. 状態の変更
状態の追跡とUIの更新は、アプリケーションの中核をなす機能です。Qwikは、アプリケーションの状態を追跡するためのuseSignal
フックを提供します。詳細については、状態管理 を参照してください。
状態を宣言するには
qwik
からuseSignal
をインポートします。import { component$, useSignal } from "@builder.io/qwik";
useSignal()
を使用してコンポーネントの状態を宣言します。const isFavoriteSignal = useSignal(false);
- 閉じている
Form
タグの後に、状態を変更するためのボタンをコンポーネントに追加します。<button onClick$={() => { isFavoriteSignal.value = !isFavoriteSignal.value; }}> {isFavoriteSignal.value ? '❤️' : '🤍'} </button>
注:ボタンをクリックすると状態が更新され、それに伴いUIも更新されます。
参考までに、このセクションの完全なコードスニペットを以下に示します。
import { component$, useSignal } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export const useJokeVoteAction = routeAction$((props) => {
console.log('VOTE', props);
});
export default component$(() => {
const isFavoriteSignal = useSignal(false);
// Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
const dadJokeSignal = useDadJoke();
const favoriteJokeAction = useJokeVoteAction();
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
<Form action={favoriteJokeAction}>
<input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
<button name="vote" value="up">
👍
</button>
<button name="vote" value="down">
👎
</button>
</Form>
<button
onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
>
{isFavoriteSignal.value ? '❤️' : '🤍'}
</button>
</section>
);
});
5. タスクとサーバーコードの呼び出し
Qwikでは、タスク は、状態が変化したときに発生する必要がある作業です。(これは他のフレームワークでの「効果」と似ています。)この例では、タスクを使用してサーバー上のコードを呼び出します。
-
qwik
からuseTask$
とqwik-city
から$server
をインポートします。import { component$, useSignal, useTask$ } from "@builder.io/qwik"; import { routeLoader$, Form, routeAction$, server$, } from '@builder.io/qwik-city';
-
isFavoriteSignal
状態を追跡する新しいタスクを作成します。useTask$(({ track }) => {});
-
isFavoriteSignal
状態の変化時にタスクを再実行するために、track
呼び出しを追加します。useTask$(({ track }) => { track(() => isFavoriteSignal.value); });
-
状態の変化時に実行する作業を追加します。
useTask$(({ track }) => { track(() => isFavoriteSignal.value); console.log('FAVORITE (isomorphic)', isFavoriteSignal.value); });
-
サーバーでのみ作業を実行する場合は、
server$()
でラップします。useTask$(({ track }) => { track(() => isFavoriteSignal.value); console.log('FAVORITE (isomorphic)', isFavoriteSignal.value); server$(() => { console.log('FAVORITE (server)', isFavoriteSignal.value); })(); });
注記
useTask$
の本体は、サーバーとクライアントの両方で実行されます(アイソモーフィック)。- SSRでは、サーバーは
FAVORITE (isomorphic) false
とFAVORITE (server) false
を出力します。 - ユーザーがお気に入りに操作すると、クライアントは
FAVORITE (isomorphic) true
を出力し、サーバーはFAVORITE (server) true
を出力します。
参考までに、このセクションの完全なコードスニペットを以下に示します。
import { component$, useSignal, useTask$ } from '@builder.io/qwik';
import {
routeLoader$,
Form,
routeAction$,
server$,
} from '@builder.io/qwik-city';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export const useJokeVoteAction = routeAction$((props) => {
console.log('VOTE', props);
});
export default component$(() => {
const isFavoriteSignal = useSignal(false);
// Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
const dadJokeSignal = useDadJoke();
const favoriteJokeAction = useJokeVoteAction();
useTask$(({ track }) => {
track(() => isFavoriteSignal.value);
console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
server$(() => {
console.log('FAVORITE (server)', isFavoriteSignal.value);
})();
});
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
<Form action={favoriteJokeAction}>
<input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
<button name="vote" value="up">
👍
</button>
<button name="vote" value="down">
👎
</button>
</Form>
<button
onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
>
{isFavoriteSignal.value ? '❤️' : '🤍'}
</button>
</section>
);
});
6. スタイル
スタイリングは、あらゆるアプリケーションにおいて重要な部分です。Qwikは、コンポーネントにスタイルを関連付け、スコープを設定する方法を提供します。
スタイルを追加するには
-
新しいファイル
src/routes/joke/index.css
を作成します。p { font-weight: bold; } form { float: right; }
-
src/routes/joke/index.tsx
でスタイルをインポートします。import styles from "./index.css?inline";
-
qwik
からuseStylesScoped$
をインポートします。import { component$, useSignal, useStylesScoped$, useTask$ } from "@builder.io/qwik";
-
コンポーネントにスタイルを読み込ませます。
useStylesScoped$(styles);
コードの説明
?inline
クエリパラメータは、Viteにスタイルをコンポーネントにインライン化することを指示します。useStylesScoped$
の呼び出しは、Qwikにスタイルをコンポーネントのみに関連付ける(スコープを設定する)ことを指示します。- スタイルは、SSRの一部として既にインライン化されている場合、および最初のコンポーネントの場合のみ読み込まれます。
参考までに、このセクションの完全なコードスニペットを以下に示します。
import {
component$,
useSignal,
useStylesScoped$,
useTask$,
} from '@builder.io/qwik';
import {
routeLoader$,
Form,
routeAction$,
server$,
} from '@builder.io/qwik-city';
import styles from './index.css?inline';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export const useJokeVoteAction = routeAction$((props) => {
console.log('VOTE', props);
});
export default component$(() => {
useStylesScoped$(styles);
const isFavoriteSignal = useSignal(false);
// Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
const dadJokeSignal = useDadJoke();
const favoriteJokeAction = useJokeVoteAction();
useTask$(({ track }) => {
track(() => isFavoriteSignal.value);
console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
server$(() => {
console.log('FAVORITE (server)', isFavoriteSignal.value);
})();
});
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
<Form action={favoriteJokeAction}>
<input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
<button name="vote" value="up">👍</button>
<button name="vote" value="down">👎</button>
</Form>
<button
onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
>
{isFavoriteSignal.value ? '❤️' : '🤍'}
</button>
</section>
);
});
7. プレビュー
このチュートリアルでは、主要なQwikの概念とそのAPIの概要として、基本的な例題アプリケーションを示しています。アプリケーションは開発モードで実行されており、ホットモジュールリロード(HMR)を使用して、コードの変更中にアプリケーションを継続的に更新します。
開発モードでは
- 各ファイルは個別にロードされるため、ネットワークタブにウォーターフォールが発生する可能性があります。
- バンドルの推測的な読み込みがないため、最初のインタラクションに遅延が発生する可能性があります。
これらの問題を解消する本番ビルドを作成しましょう。
プレビュービルドを作成するには
npm run preview
を実行して、本番ビルドを作成します。
注記
- これで、アプリケーションは別のポートで本番ビルドを実行するはずです。
- ここでアプリケーションと対話すると、開発ツールのネットワークタブに、バンドルがServiceWorkerキャッシュから即座に配信されていることが表示されます。
レビュー
おめでとうございます!Qwikについて多くのことを学びました!Qwikで達成できることの詳細については、このチュートリアルで触れられた各トピックに関する専用のドキュメントをご覧ください。