🚀

QwikをQuick入門

2024/12/23に公開

概要

QwikをQuick入門したい。
公式チュートリアルをやっていく
https://qwik.dev/docs/getting-started/

リポ
https://github.com/thirdlf03/qwik-quick

セットアップ

今回は、Playground Appで作成

npm create qwik@latest
cd qwik-app
npm start

Hello World

mkdir src/routes/hello
touch src/routes/hello/index.tsx

index.tsx

import { component$ } from '@builder.io/qwik'  
  
export default component$(() => {  
  return (  
      <h1>Hello, World!</h1>  
  )  
})

これで、http://localhost:5173/hello/
にアクセスするとHello Worldできる。

WebAPIからデータ取得

サーバーからクライアントへ

今回は、zennのapi使用
https://zenn.dev/api/articles?username=thirdlf&order=latest

routeLoaderを使うといいらしい

import { component$ } from '@builder.io/qwik'
import { routeLoader$ } from '@builder.io/qwik-city';  
  
export const getZennArticle = routeLoader$(async () => {  
    const response = await fetch("https://zenn.dev/api/articles?username=thirdlf&order=latest");  
    return response.json();  
})  
  
export default component$(() => {  
    const zennArticles = getZennArticle();  
    return (  
        <div>  
            <h1>Hello, World!</h1>  
            <div>
                {zennArticles.value.articles.map((article: any) => (  
                    <div key={article.id} align="center">  
                        <p>記事名: {article.title}</p>  
                        <p>いいね数: {article.liked_count}</p>  
                    </div>
                ))}  
            </div>  
        </div>
    )  
})

ふむ

Form使ってみる

クライアントからサーバーへ

routeActions$を使う

import { component$ } from '@builder.io/qwik'  
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';  
  
export const useZennArticle = routeLoader$(async () => {  
    const response = await fetch("https://zenn.dev/api/articles?username=thirdlf&order=latest");  
    return response.json();  
})  
  
export const useVoteAction = routeAction$((props) => {  
    console.log('VOTE', props);  
});  
  
export default component$(() => {  
    const zennArticles = useZennArticle();  
    const voteAction = useVoteAction();  
    return (  
        <div>  
            <h1>Hello, World!</h1>  
            <div>
                {zennArticles.value.articles.map((article: any) => (  
                    <div key={article.id} align="center">  
                        <p>記事名: {article.title}</p>  
                        <p>いいね数: {article.liked_count}</p>  
                        <Form action={voteAction}>  
                            <input type="hidden" name="voteID" value={article.id} />  
                            <button name="vote" value="up">👍</button>  
                            <button name="vote" value="down">👎</button>  
                        </Form>
                    </div>
                ))}  
            </div>  
        </div>
)  
})

状態管理

reactでいう、useStateかな
useSignalを使う

import { component$, useSignal } from '@builder.io/qwik'  
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';  
  
export const useZennArticle = routeLoader$(async () => {  
    const response = await fetch("https://zenn.dev/api/articles?username=thirdlf&order=latest");  
    return response.json();  
})  
  
export const useVoteAction = routeAction$((props) => {  
    console.log('VOTE', props);  
});  
  
export default component$(() => {  
    const isLike = useSignal(false);  
    const zennArticles = useZennArticle();  
    const voteAction = useVoteAction();  
    return (  
        <div>  
            <h1>Hello, World!</h1>  
            <div>
                {zennArticles.value.articles.map((article: any) => (  
                    <div key={article.id} align="center">  
                        <p>記事名: {article.title}</p>  
                        <button onClick$={() => {  
                            article.liked_count > 0 ? isLike.value = !isLike.value : isLike.value = false;  
                        }}>いいねチェック  
                        </button>  
                        {isLike.value ? <p>いいね数: {article.liked_count}</p> : <p>いいね数チェックしてね</p>}  
                        <Form action={voteAction}>  
                            <input type="hidden" name="voteID" value={article.id} />  
                            <button name="vote" value="up">👍</button>  
                            <button name="vote" value="down">👎</button>  
                        </Form>
                    </div>
                ))}  
            </div>  
        </div>
    )  
})

状態の監視

useTask$
これは、clientでもserverでもどっちの場合でも実行される
useTask$` is executed on both the server and the client

import { component$, useSignal, useTask$ } from '@builder.io/qwik'  
import { routeLoader$, Form, routeAction$, server$ } from '@builder.io/qwik-city';  
  
export const useZennArticle = routeLoader$(async () => {  
    const response = await fetch("https://zenn.dev/api/articles?username=thirdlf&order=latest");  
    return response.json();  
})  
  
export const useVoteAction = routeAction$((props) => {  
    console.log('VOTE', props);  
});  
  
export default component$(() => {  
    const isLike = useSignal(false);  
    const zennArticles = useZennArticle();  
    const voteAction = useVoteAction();  
  
    useTask$(({ track }) => {  
        track(() => isLike.value);  
        console.log('(isomorphic)', isLike.value);  
        server$(() => {  
            console.log('(server)', isLike.value);  
        })();  
    });  
  
    return (  
        <div>  
            <h1>Hello, World!</h1>  
            <div>
                {zennArticles.value.articles.map((article: any) => (  
                    <div key={article.id} align="center">  
                        <p>記事名: {article.title}</p>  
                        <button onClick$={() => {  
                            article.liked_count > 0 ? isLike.value = !isLike.value : isLike.value = false;  
                        }}>いいねチェック  
                        </button>  
                        {isLike.value ? <p>いいね数: {article.liked_count}</p> : <p>いいね数チェックしてね</p>}  
                        <Form action={voteAction}>  
                            <input type="hidden" name="voteID" value={article.id} />  
                            <button name="vote" value="up">👍</button>  
                            <button name="vote" value="down">👎</button>  
                        </Form>
                    </div>
                ))}  
            </div>  
        </div>
    )  
})

Styling

helloディレクトリにcssファイルを追加する

touch src/routes/hello/index.css

index.css

p {
  font-weight: bold;
  color: red;
}

cssを読み込む
index.tsx

import { component$, useSignal, useTask$, useStylesScoped$ } from '@builder.io/qwik'  
import { routeLoader$, Form, routeAction$, server$ } from '@builder.io/qwik-city';  
import styles from "./index.css?inline";  
  
export const useZennArticle = routeLoader$(async () => {  
    const response = await fetch("https://zenn.dev/api/articles?username=thirdlf&order=latest");  
    return response.json();  
})  
  
export const useVoteAction = routeAction$((props) => {  
    console.log('VOTE', props);  
});  
  
export default component$(() => {  
    useStylesScoped$(styles);  
    const isLike = useSignal(false);  
    const zennArticles = useZennArticle();  
    const voteAction = useVoteAction();  
  
    useTask$(({ track }) => {  
        track(() => isLike.value);  
        console.log('(isomorphic)', isLike.value);  
        server$(() => {  
            console.log('(server)', isLike.value);  
        })();  
    });  
  
    return (  
        <div>  
            <h1>Hello, World!</h1>  
            <div>
                {zennArticles.value.articles.map((article: any) => (  
                    <div key={article.id} align="center">  
                        <p>記事名: {article.title}</p>  
                        <button onClick$={() => {  
                            article.liked_count > 0 ? isLike.value = !isLike.value : isLike.value = false;  
                        }}>いいねチェック  
                        </button>  
                        {isLike.value ? <p>いいね数: {article.liked_count}</p> : <p>いいね数チェックしてね</p>}  
                        <Form action={voteAction}>  
                            <input type="hidden" name="voteID" value={article.id} />  
                            <button name="vote" value="up">👍</button>  
                            <button name="vote" value="down">👎</button>  
                        </Form>
                    </div>
                ))}  
            </div>  
        </div>
    )  
})

preview

previewなるものがあるらしい
本番ビルドで試せる

npm run preview

比較用

dev

npm start

production

npm run preview

若干パフォーマンス良くなってる

まとめ

コンポーネント作成

component$ を使って、Qwikコンポーネントを定義する。

データ取得

routeLoader$ を使って、データを取得する。 サーバーサイドで実行され、必要なデータを取得してからページを表示。

アクション

routeAction$ を使って、ユーザーのインタラクションなどのイベントをトリガー。フォームの送信やボタンのクリックなど、状態変更を伴う操作で使用する。

状態管理

useSignal を使って、状態管理。

タスク実行と追跡

useTask$ を使って、副作用や状態の追跡を実装。状態の変化やデータの同期処理に利用する。

CSSの反映

useStylesScoped$ を使って、コンポーネント単位でスコープ化されたCSSを適用。

完走した感想

書いてる感じはほぼreact

GitHubで編集を提案

Discussion