EY-Office ブログ

ちょっと変わったフレームワークQwikに触れて驚いた(1)

最近Qwikというフロントエンド・フレームワークの話題を目ににするようになってきました。ただしqwikをGoogleで検索すると、フロントエンド・フレームワークQwikの記事は2ページ目くらいで無くなってしまうくらいの量しかありません。

新しいフロントエンド・フレームワークは次々と登場してきますが、SPA(single-page application)か、SPA+バックエンドなフレームワークがほとんどですが、Qwikは違いそうです。

Qwik

Qwikとは何か?

ChatGPT3.5さんに聞いたところ

すみません、私の知識は2021年までのものですので、Qwikというフレームワークについては情報がありません。最新の情報についてはインターネットで調査することをおすすめします。

と回答されました。そこでBingのAIチャットさんに聞いたところ

はい、知っています。Qwikは、フロントエンドのフレームワークで、インタラクティブなアプリケーションを構築するためのものです¹²。Qwikの特徴は、ユーザーがトリガーしたアクションに必要なコードだけをダウンロードして実行するということです¹³。これにより、モバイルデバイスでも瞬時に起動するアプリケーションを実現できます³⁴。 詳細情報は省略

「Qwikとは何か?」に付いの解説は次回のブログに書きます(今すぐ知りたい人は他の方の書いた記事を読んでください)。

Qwikを使ってみた

新しい概念をもたらすフレームワークというと、従来と違う記法やルールが導入されていて戸惑う事が多いと思います。たとえばjQueryを使ってフロントエンドを作ってきた人が初めてReactに触れると、なぜ一覧表を表示するのにmapメソッドを使うの? JSXって何だ?? ステート(State)って何??? などなど戸惑うことが多いと思います。

今回、Qwikを知るために、このブログによく登場するジャンケンアプリをQwikで書いてみる事にしましたが。
ところが、React用に書かれたたコードが、ほんの少しの変更でQwikに変換できて、驚きました。😲

Reactのコードから変換されたQwikコードの違いをまとめてみました。

トップレベル・コンポーネント

React
import React, { useState } from 'react';
import type { Te } from './JyankenBox';
import JyankenBox from './JyankenBox'
import type { ScoreType, Judgment } from './ScoreBox';
import ScoreBox from './ScoreBox'

const Jyanken: React.FC = () => {
  const [scores, setScrores] = useState<ScoreType[]>([]);

  const pon = (human: Te) => {
    const computer: Te = Math.floor(Math.random() * 3);
    const judgment: Judgment = (computer - human + 3) % 3;
    const score = {human: human, computer: computer, judgment: judgment};
    setScrores([score, ...scores]);
  }

  return (
    <>
      <h1>じゃんけん ポン!</h1>
      <JyankenBox actionPon={pon)} />
      <ScoreBox scores={scores} />
    </>
  );
}

export default Jyanken;
Qwik
  • ① Reactではコンポーネントは通常の関数ですが、Qwikではcomponent$()関数を使って定義します
  • ② React同様ステートがあります、ステートの定義useSignal()またはuseStore() 関数(Hooks)を使います
    • useStoreはオブジェクトを管理できます、さらにネストしたオブジェクトや配列も管理できます
  • $()はjQueryではありませんよ😅。 Qwikの特殊な式定義です。詳細は次回
  • ④ ステート更新はReactととは異なり、代入等の破壊的操作で行います
import { component$, $, useStore } from '@builder.io/qwik';
import type { Te } from './JyankenBox';
import JyankenBox from './JyankenBox'
import type { ScoreType, Judgment } from './ScoreBox';
import ScoreBox from './ScoreBox'

export default component$(() => {                       // ← ①
  const scores = useStore<ScoreType[]>([]);             // ← ②

  const pon = $((human: Te) => {                        // ← ③
    const computer: Te = Math.floor(Math.random() * 3);
    const judgment: Judgment = (computer - human + 3) % 3;
    const score = {human: human, computer: computer, judgment: judgment};
    scores.push(score);                                // ← ④
  });

  return (
    <>
      <h1>じゃんけん ポン!</h1>
      <JyankenBox actionPon={pon} />
      <ScoreBox scores={scores} />
    </>
   );
});

JyankenBoxコンポーネント

React
import React from 'react'

export enum Te { Guu = 0, Choki, Paa}

type JyankenBoxProps = {
  actionPon: (te: number) => void;
}
const JyankenBox: React.FC<JyankenBoxProps> = ({actionPon}) => {
  const divStyle = {margin: "0 20px"};
  const buttonStyle = {margin: "0 10px", padding: "3px 10px", fontSize: 14};
  return (
    <div style={divStyle}>
      <button onClick={() => actionPon(Te.Guu)} style={buttonStyle}>グー</button>
      <button onClick={() => actionPon(Te.Choki)} style={buttonStyle}>チョキ</button>
      <button onClick={() => actionPon(Te.Paa)} style={buttonStyle}>パー</button>
    </div>
  );
}

export default JyankenBox;
Qwik
  • ① コンポーネントの定義component$()、引数(Props)の型定義はジェネリクスで指定するのもReactと同様ですね
  • ② ボタンのクリックハンドラーはonClick$で定義します、また$が出てきましたね。詳細は次回
import { component$ } from '@builder.io/qwik';

export enum Te { Guu = 0, Choki, Paa}

type JyankenBoxProps = {
  actionPon: (te: number) => void;
}
const JyankenBox = component$<JyankenBoxProps>(({actionPon}) => {  // ← ①
  const divStyle = {margin: "0 20px"};
  const buttonStyle= {margin: "4px 10px", padding: "3px 10px", fontSize: 14};
  return (
    <div style={divStyle}>
               // ↓ ②
      <button onClick$={() => actionPon(Te.Guu)} style={buttonStyle}>グー</button>
      <button onClick$={() => actionPon(Te.Choki)} style={buttonStyle}>チョキ</button>
      <button onClick$={() => actionPon(Te.Paa)} style={buttonStyle}>パー</button>
    </div>
  );
});

export default JyankenBox;

ScoreBoxコンポーネント

React
import React from 'react'

export enum Judgment { Draw = 0, Win, Lose }
export type ScoreType = {
  human: number;
  computer: number;
  judgment: Judgment;
};

type ScoreListProps = {
  scores: ScoreType[];
};

const ScoreBox: React.FC<ScoreListProps> = ({scores}) => {
  const teString = ["グー","チョキ", "パー"];
  const judgmentString = ["引き分け","勝ち", "負け"];

  const tableStyle = {marginTop: 20, borderCollapse: "collapse"};
  const thStyle = {border: "solid 1px #888", padding: "3px 15px"};
  const tdStyle = {border: "solid 1px #888", padding: "3px 15px", textAlign: "center"};

  return (
    <table style={tableStyle}>
      <thead>
        <tr>
          <th style={thStyle}>あなた</th>
          <th style={thStyle}>コンピュター</th>
          <th style={thStyle}>勝敗</th>
        </tr>
      </thead>
      <tbody>
        {scores.map((scrore, ix) =>
          <tr key={ix}>
            <td style={tdStyle}>{teString[scrore.human]}</td>
            <td style={tdStyle}>{teString[scrore.computer]}</td>
            <td style={tdStyle}>{judgmentString[scrore.judgment]}</td>
          </tr>
        )}
      </tbody>
    </table>
  );
}

export default ScoreBox;
Qwik
  • ① コンポーネントの定義component$()は既に説明しましたよね

このコンポーネントの変更点はここだけです!

import { component$ } from '@builder.io/qwik';

export enum Judgment { Draw = 0, Win, Lose }

export type ScoreType = {
  human: number;
  computer: number;
  judgment: Judgment;
};


type ScoreListProps = {
  scores: ScoreType[];
}
const ScoreBox = component$<ScoreListProps>(({scores}) => {   // ← ①
  const teString = ["グー","チョキ", "パー"];
  const judgmentString = ["引き分け","勝ち", "負け"];

  const tableStyle = {marginTop: 20, borderCollapse: "collapse"};
  const thStyle = {border: "solid 1px #888", padding: "3px 15px"};
  const tdStyle = {border: "solid 1px #888", padding: "3px 15px", textAlign: "center"};

  return (
    <table style={tableStyle}>
      <thead>
        <tr>
          <th style={thStyle}>あなた</th>
          <th style={thStyle}>コンピュター</th>
          <th style={thStyle}>勝敗</th>
        </tr>
      </thead>
      <tbody>
        {scores.map((scrore, ix) =>
          <tr key={ix}>
            <td style={tdStyle}>{teString[scrore.human]}</td>
            <td style={tdStyle}>{teString[scrore.computer]}</td>
            <td style={tdStyle}>{judgmentString[scrore.judgment]}</td>
          </tr>
        )}
      </tbody>
    </table>
  );
});

export default ScoreBox;

以上のように、ごく僅かな変更でQwikになりました。

まとめ

通常、新しい技術の紹介記事は、その技術の特徴やなぜ開発されたかの解説から始めます。しかし今回のブログを書くにあたり、Qwikを学びならがらサンプルコードを作ったところ、Reactのコードがほとんどそのまま使えるのに驚いたので、サンプルコードからはじめました。

Qwikは新しいフロントエンド・フレームワークですが、

  • コンポーネント指向
    • コンポーネントは関数で定義
    • コンポーネントには引数(Props)が渡せる
  • HTML生成はJSXを使う
  • Stateがある、ただし管理関数は今風のSignal
    • SignalはSolid JSPreactなど最近のフレームワーク(ライブラリー)で使われています
  • Hookを使う
  • TypeScriptで書ける

などReactで使われている技術を採用しているので、Reactのコードの再利用が簡単にできます。そういえば以前しらべた話題のSolid JSに入門してみたもReactの技術を利用していましたね。

やはり、フォロントエンドフレームワークでは圧倒的に利用されているReactを無視する事は出来ないのです。
新しいフレームワークを作る人は、何らの形でReactから容易に移行できるよう作らないと広く受け入れられないのでしょうね。

State of JavaScript 2022 https://2022.stateofjs.com/ja-JP/libraries/front-end-frameworks/ より

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ