Qwik React ⚛️
Qwik React を使用すると、React を Qwik 内で使用できます。Qwik React を使用する利点は、既存の React コンポーネントとライブラリを Qwik 内で使用できることです。これにより、Material UI、Threejs、React Spring など、React コンポーネントとライブラリの大規模なエコシステムを利用できます。また、React アプリケーションを書き直すことなく、Qwik の利点を得るための良い方法でもあります。
基本的な使い方
Qwik React の基本的な使い方は、既存の React コンポーネントを取得し、qwikify$ 関数でラップすることです。この関数は、Qwik 内で使用できる Qwik コンポーネントを作成し、React コンポーネントをアイランドに変えて、React コンポーネントをいつハイドレーションするかを微調整する自由を与えます。
基本的な使い方
// This pragma is required so that React JSX is used instead of Qwik JSX
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
 
// An existing React component
function Greetings() {
  return <div>Hello from React</div>;
}
 
// Qwik component wrapping the React component
export const QGreetings = qwikify$(Greetings);0. インストール
Qwik React を使用する前に、Qwik プロジェクトが Qwik React を使用するように構成する必要があります。最も簡単な方法は、次のコマンドを実行することです。
まだ Qwik アプリがない場合は、最初に 作成 する必要があり、その後、指示に従って、アプリに React を追加するコマンドを実行します。
npm run qwik add react上記のコマンドは、以下を実行します。
- package.jsonに必要な依存関係をインストールします- { ..., "dependencies": { ..., "@builder.io/qwik-react": "0.5.0", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "react": "18.2.0", "react-dom": "18.2.0", } }- 注: これは React のエミュレーションではありません。実際の React ライブラリを使用しています。 
- @builder.io/qwik-reactプラグインを使用するように Vite を構成します- // vite.config.ts import { qwikReact } from '@builder.io/qwik-react/vite'; export default defineConfig(() => { return { ..., plugins: [ ..., // The important part qwikReact() ], }; });
注:
npm run qwik add reactは、Qwik React 統合を紹介するデモルートも構成します。これらは
package.jsondependencies
@emotion/react 11.10.6
@emotion/styled 11.10.6
@mui/material 5.11.9
@mui/x-data-grid 5.17.24
src/route:
/src/routes/react: React 統合を紹介する新しいパブリックルート
/src/integrations/react: ここに React コンポーネントがありますこのガイドではこれらを無視し、代わりに最初からプロセスを説明します。
1. Hello World
簡単な例から始めましょう。簡単な React コンポーネントを作成し、それを Qwik コンポーネントでラップします。次に、Qwik ルートで Qwik コンポーネントを使用します。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
 
// Create React component standard way
function Greetings() {
  return <p>Hello from React</p>;
}
 
// Convert React component to Qwik component
export const QGreetings = qwikify$(Greetings);@builder.io/qwik-react パッケージは、React コンポーネントをアプリケーション全体で使用できる Qwik コンポーネントに変換できる qwikify$() 関数をエクスポートします。
注:
qwikify$()を使用して最初に変換しないと、Qwik で React コンポーネントを使用することはできません。React コンポーネントと Qwik コンポーネントは似ていますが、根本的に大きく異なります。
ReactとQwikのコンポーネントは同じファイル内で混在させることはできません。インストールコマンドを実行直後のプロジェクトを確認すると、src/integrations/react/という新しいフォルダがあるのがわかるでしょう。Reactコンポーネントはそこに配置することをお勧めします。
2. Reactアイランドのハイドレーション
上記の例では、サーバー上で静的なReactコンテンツをSSRする方法を示しています。利点は、そのコンポーネントがブラウザで再レンダリングされることがなく、したがって、そのコードがクライアントにダウンロードされることもないことです。しかし、コンポーネントがインタラクティブである必要があり、したがってブラウザでその動作をダウンロードする必要がある場合はどうでしょうか? まず、Reactでシンプルなカウンターの例を作成することから始めましょう。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { useState } from 'react';
 
// Create React component standard way
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button className="react" onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
 
// Convert React component to Qwik component
export const QCounter = qwikify$(Counter);Countボタンをクリックしても何も起こらないことに注目してください。これは、Reactがダウンロードされておらず、したがってコンポーネントがハイドレートされていないためです。ReactコンポーネントをダウンロードしてハイドレートするようにQwikに指示する必要がありますが、そのための条件を指定する必要があります。それを早急に行うと、Reactアプリケーションをアイランドに変えることの利点がすべて失われます。この場合、ユーザーがボタンにカーソルを合わせたときにコンポーネントをダウンロードしたいので、qwikify$()に{: eagerness: 'hover' }を追加します。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { useState } from 'react';
 
// Create React component standard way
function Counter() {
  // Print to console to show when the component is rendered.
  console.log('React <Counter/> Render');
  const [count, setCount] = useState(0);
  return (
    <button className="react" onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
 
// Specify eagerness to hydrate component on hover event.
export const QCounter = qwikify$(Counter, { eagerness: 'hover' });この例では、舞台裏で何が起こっているかを示すためにコンソールを立ち上げました。ボタンにカーソルを合わせると、Reactコンポーネントがレンダリングされるのがわかります。これは、hover時にコンポーネントをハイドレートするようにQwikに指示したためです。コンポーネントがハイドレートされたので、操作するとカウントが正しく更新されます。
qwikify$()にeagernessプロパティを指定することで、コンポーネントがハイドレートされる条件を微調整できるようになり、アプリケーションの起動パフォーマンスに影響を与えます。
3. アイランド間の通信
前の例では、遅延ハイドレートした単一のアイランドがありました。しかし、複数のアイランドがある場合は、それらの間で通信する必要が出てきます。この例では、Qwikでアイランド間の通信を行う方法を示します。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
 
function Button({ onClick }: { onClick: () => void }) {
  console.log('React <Button/> Render');
  return <button onClick={onClick}>+1</button>;
}
 
function Display({ count }: { count: number }) {
  console.log('React <Display count=' + count + '/> Render');
  return <p className="react">Count: {count}</p>;
}
 
export const QButton = qwikify$(Button, { eagerness: 'hover' });
export const QDisplay = qwikify$(Display);上記の例では、ボタン用(QButton)と表示用(QDisplay)の2つのアイランドがあります。ボタンアイランドはhover時にハイドレートされ、表示アイランドはいかなるイベントでもハイドレートされません。
react.tsxファイルには次のものがあります。
- QButton- クリックするとカウントを増やすボタン。このアイランドは- hover時にハイドレートされます。
- QDisplay- 現在のカウントを表示するディスプレイ。このアイランドはいかなるイベントでもハイドレートされませんが、そのpropsが変更されるとQwikによってハイドレートされます。
- 両方のReactコンポーネントには、コンポーネントがハイドレートまたは再レンダリングされたときに表示するconsole.logがあります。
index.tsxファイルには次のものがあります。
- count- 現在のカウントを追跡するために使用されるシグナル。
- QButtonアイランドをインスタンス化します。- onClick$ハンドラーは- countシグナルを増分します。Qwikは- onClickを- onClick$プロップに自動的に変換し、イベントハンドラーの遅延読み込みを可能にすることに注意してください。
- QDisplayアイランドをインスタンス化します。- countシグナルはpropとしてアイランドに渡されます。
ボタンにカーソルを合わせると、QButtonアイランドがハイドレートされるのがわかります。ボタンをクリックすると、QDisplayアイランドがハイドレートされ、カウントが更新されるのがわかります。(QDisplayの二重実行は、最初に初期ハイドレーションが行われ、次にカウントが更新されるためです。)
Qwik Reactは、インタラクティビティを持つコンポーネントのみを早急にハイドレートする必要があることに注意してください。動的ではあるが、インタラクティビティを持たないコンポーネント(この例のQDisplayなど)は、早急にハイドレートする必要はなく、代わりにpropsが変更されると自動的にハイドレートされます。
また、ブラウザでconsole.log('Qwik Render');が実行されないことにも注意してください。
4. host:リスナー
前の例では、2つのアイランドがありました。QButtonは、ReactがonClickイベントハンドラーを設定できるように、早急にハイドレートする必要がありました。これは、QButtonアイランドの出力が静的であるため、再レンダリングする必要がないため、少し無駄です。QButtonをクリックしても、QButtonアイランドは再レンダリングされません。そのような場合、Qwikに、リスナーをアタッチするためだけにReactでコンポーネント全体をハイドレートするのではなく、clickリスナーを登録するように依頼できます。これは、イベント名にhost:プレフィックスを使用することで行われます。
import { component$, useSignal } from '@builder.io/qwik';
import { QButton, QDisplay } from './react';
 
export default component$(() => {
  console.log('Qwik Render');
  const count = useSignal(0);
  return (
    <main>
      <QButton
        host:onClick$={() => {
          console.log('click', count.value);
          count.value++;
        }}
      >
        +1
      </QButton>
      <QDisplay count={count.value}></QDisplay>
    </main>
  );
});ボタンにカーソルを合わせても何も起こりません(Reactハイドレーションなし)。ボタンをクリックすると、Qwikがイベントを処理してシグナルを更新し、その結果QDisplayアイランドがハイドレートされます。QButtonアイランドは決してハイドレートされないことに注意してください。したがって、この変更により、サーバー側のみでレンダリングされ、ブラウザでハイドレートする必要がないアイランドを持つことができ、ユーザーの時間を節約できます。
5. childrenの投影
一般的なユースケースは、コンテンツの子をコンポーネントに渡すことです。これはQwik Reactでも同様に機能します。Reactコンポーネントでは、propsにchildrenを宣言し、予想どおりに使用するだけです(react.tsxを参照)。
import { component$, useSignal } from '@builder.io/qwik';
import { QFrame } from './react';
 
export default component$(() => {
  console.log('Qwik Render');
  const count = useSignal(0);
  return (
    <QFrame>
      <button
        onClick$={() => {
          console.log('click', count.value);
          count.value++;
        }}
      >
        +1
      </button>
      Count: {count}
    </QFrame>
  );
});QFrameアイランドには、ハイドレーションをトリガーするeagernessまたはpropsがないため、決してハイドレートされないことに注意してください。しかし、シグナルが変化すると子が再レンダリングされ、アイランドがハイドレートされなくても、React QFrameアイランドに正しく投影されることにも注意してください。これにより、ページをさらにサーバー側でレンダリングし、クライアントでレンダリングしないようにすることができます。
6. Reactライブラリの使用
最後に、QwikアプリケーションでReactライブラリを使用することができます。この例では、Material UIとEmotionを使用して、このReactの例をレンダリングしています。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
import { type ReactNode } from 'react';
 
export const Example = qwikify$(
  function Example({
    selected,
    onSelected,
    children,
  }: {
    selected: number;
    onSelected: (v: number) => any;
    children?: ReactNode[];
  }) {
    console.log('React <Example/> Render');
    return (
      <>
        <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
          <Tabs
            value={selected}
            onChange={(e, v) => onSelected(v)}
            aria-label="basic tabs example"
          >
            <Tab label="Item One" />
            <Tab label="Item Two" />
            <Tab label="Item Three" />
          </Tabs>
          {children}
        </Box>
      </>
    );
  },
  { eagerness: 'hover' }
);Reactの例はカーソルを合わせたときにハイドレートされ、期待どおりに機能します。
ルール
Qwik Reactのルールをより深く理解するために、この例を見てみましょう。
/** @jsxImportSource react */
 
import { qwikify$ } from '@builder.io/qwik-react';
import { Alert, Button, Slider } from '@mui/material';
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid';
 
export const MUIButton = qwikify$(Button);
export const MUIAlert = qwikify$(Alert);
export const MUISlider = qwikify$(Slider, { eagerness: 'hover' });重要:ファイルの先頭に
/** @jsxImportSource react */をインポートする必要があります。これは、コンパイラーにReactをJSXファクトリとして使用するように指示するものです。
要するに、ルールは次のとおりです。
- ReactとQwikのコンポーネントを同じファイル内で混在させないでください。
- すべてのReactコードをsrc/integrations/reactフォルダー内に配置することをお勧めします。
- Reactコードを含むファイルの先頭に/** @jsxImportSource react */を追加します。
- qwikify$()を使用して、ReactコンポーネントをQwikコンポーネントに変換し、Qwikモジュールからインポートできるようにします。
これで、QwikはMUIButtonをインポートして、他のQwikコンポーネントと同様に使用できるようになりました。
import { component$ } from '@builder.io/qwik';
import { MUIAlert, MUIButton } from '~/integrations/react/mui';
 
export default component$(() => {
  return (
    <>
      <MUIButton client:hover>Hello this is a button</MUIButton>
 
      <MUIAlert severity="warning">This is a warning from Qwik</MUIAlert>
    </>
  );
});qwikify$()
qwikify$(ReactCmp, options?): QwikCmpを使用すると、Reactコンポーネントの部分的なハイドレーションを実装できます。これは、ReactのSSRおよびハイドレーションロジックを、SSR中にReactのrenderToString()を実行し、指定された場合にhydrateRoot()を動的に呼び出すことができるQwikコンポーネントにラップすることで機能します。
デフォルトではブラウザでReactコードは実行されないことに注意してください。つまり、Reactコンポーネントはデフォルトではインタラクティブではありません。たとえば、次の例では、MUIのSliderコンポーネントをqwikifyしますが、インタラクティブではありません(Reactコンポーネントをブラウザでハイドレートする時期をQwikに指示するeagernessプロパティがありません)。
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { Slider } from '@mui/material';
export const MUISlider = qwikify$<typeof Slider>(
  Slider
  //  Uncomment next line to make component interactive in browser
  // { eagerness: 'hover' }
);制限事項
すべてのqwikified Reactコンポーネントは分離されています
qwikified Reactコンポーネントの各インスタンスは、独立したReactアプリになります。完全に隔離されています。
export const MUISlider = qwikify$(Slider);
 
<MUISlider></MUISlider>
<MUISlider></MUISlider>- 各MUISliderは、独自のステート、ライフサイクルなどを持つ完全に隔離されたReactアプリケーションです。
- スタイルは重複します
- 状態は共有されません
- コンテキストは継承されません。
- アイランドは独立してハイドレートします。
デフォルトではインタラクティビティは無効になっています
デフォルトでは、qwikifiedコンポーネントはインタラクティブになりません。インタラクティビティを有効にする方法については、次のセクションを参照してください。
qwikify$()を移行戦略として使用する
QwikでReactコンポーネントを使用することは、アプリケーションをQwikに移行するための優れた方法ですが、万能薬ではありません。Qwikの機能を活用するために、コンポーネントを書き換える必要があります。
threejsやdata-grid libsなど、Reactエコシステムを楽しむための優れた方法でもあります。
アプリケーションを構築するために
qwikify$()を乱用しないでください。乱用するとパフォーマンス上の利点が失われます。
リーフノードではなく、ワイドアイランドを構築する
たとえば、リストを作成するために複数のMUIコンポーネントを使用する必要がある場合は、個々のMUIコンポーネントをqwikifyするのではなく、リスト全体を単一のqwikified Reactコンポーネントとして構築します。
GOOD:ワイドアイランド
すべてのMUIコンポーネントを含む単一のqwikifiedコンポーネント。スタイルは重複せず、コンテキストとテーマ設定は期待どおりに機能します。
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';
import ImageIcon from '@mui/icons-material/Image';
import WorkIcon from '@mui/icons-material/Work';
import BeachAccessIcon from '@mui/icons-material/BeachAccess';
 
// Qwikify the whole list
export const FolderList = qwikify$(() => {
  return (
    <List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
      <ListItem>
        <ListItemAvatar>
          <Avatar>
            <ImageIcon />
          </Avatar>
        </ListItemAvatar>
        <ListItemText primary="Photos" secondary="Jan 9, 2014" />
      </ListItem>
      <ListItem>
        <ListItemAvatar>
          <Avatar>
            <WorkIcon />
          </Avatar>
        </ListItemAvatar>
        <ListItemText primary="Work" secondary="Jan 7, 2014" />
      </ListItem>
      <ListItem>
        <ListItemAvatar>
          <Avatar>
            <BeachAccessIcon />
          </Avatar>
        </ListItemAvatar>
        <ListItemText primary="Vacation" secondary="July 20, 2014" />
      </ListItem>
    </List>
  );
});BAD:リーフノード
リーフノードは独立してqwikifiedされます。これにより、多数のネストされたReactアプリケーションがレンダリングされ、それぞれが他のアプリケーションから完全に分離され、スタイルが重複します。
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';
import ImageIcon from '@mui/icons-material/Image';
import WorkIcon from '@mui/icons-material/Work';
import BeachAccessIcon from '@mui/icons-material/BeachAccess';
 
export const MUIList = qwikify$(List);
export const MUIListItem = qwikify$(ListItem);
export const MUIListItemText = qwikify$(ListItemText);
export const MUIListItemAvatar = qwikify$(ListItemAvatar);
export const MUIAvatar = qwikify$(Avatar);
export const MUIImageIcon = qwikify$(ImageIcon);
export const MUIWorkIcon = qwikify$(WorkIcon);
export const MUIBeachAccessIcon = qwikify$(BeachAccessIcon);// Qwik component using dozens of nested React islands
// Each MUI-* it's an independent React application
export const FolderList = component$(() => {
  return (
    <MUIList sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
      <MUIListItem>
        <MUIListItemAvatar>
          <MUIAvatar>
            <MUIImageIcon />
          </MUIAvatar>
        </MUIListItemAvatar>
        <MUIListItemText primary="Photos" secondary="Jan 9, 2014" />
      </MUIListItem>
      <MUIListItem>
        <MUIListItemAvatar>
          <MUIAvatar>
            <MUIWorkIcon />
          </MUIAvatar>
        </MUIListItemAvatar>
        <MUIListItemText primary="Work" secondary="Jan 7, 2014" />
      </MUIListItem>
      <MUIListItem>
        <MUIListItemAvatar>
          <MUIAvatar>
            <MUIBeachAccessIcon />
          </MUIAvatar>
        </MUIListItemAvatar>
        <MUIListItemText primary="Vacation" secondary="July 20, 2014" />
      </MUIListItem>
    </MUIList>
  );
});インタラクティビティの追加
インタラクティビティを追加するには、React用語で言うと、ハイドレートする必要があります。通常、Reactアプリケーションでは、このハイドレーションタスクはロード時に無条件に実行され、大規模なオーバーヘッドを追加し、サイトを遅くします。
Qwikを使用すると、client: JSXプロパティを使用して、コンポーネントをいつハイドレートするかを決定できます。この手法は、Astroによって普及した部分的なハイドレーションと呼ばれます。
export default component$(() => {
  return (
    <>
-      <MUISlider></MUISlider>
+      <MUISlider client:visible></MUISlider>
    </>
  );
});Qwikには、すぐに使用できるさまざまな戦略が付属しています
client:load
ドキュメントがロードされると、コンポーネントはすぐにハイドレートされます。
<MUISlider client:load></MUISlider>ユースケース: 可及的速やかにインタラクティブにする必要のある、すぐに表示されるUI要素。
client:idle
ブラウザが最初にアイドル状態になったとき、つまり、重要な処理がすべて実行された後に、コンポーネントはすぐにハイドレートされます。
<MUISlider client:idle></MUISlider>ユースケース: すぐにインタラクティブにする必要のない、優先度の低いUI要素。
client:visible
コンポーネントがビューポートに表示されたときに、すぐにハイドレートされます。
<MUISlider client:visible></MUISlider>ユースケース: ページの下部(「スクロールで見えない部分」)にあるか、読み込みにリソースを非常に消費するため、ユーザーが要素を見ない場合はまったく読み込まないようにしたい、優先度の低いUI要素。
client:hover
マウスがコンポーネント上にあるときに、コンポーネントはすぐにハイドレートされます。
<MUISlider client:hover></MUISlider>ユースケース: インタラクティブ性が重要ではなく、デスクトップでのみ実行する必要がある、最も優先度の低いUI要素。
client:signal
これは、渡されたシグナルがtrueになったときにいつでもコンポーネントをハイドレートできる高度なAPIです。
export default component$(() => {
  const hydrateReact = useSignal(false);
  return (
    <>
      <button onClick$={() => (hydrateReact.value = true)}>Hydrate Slider when click</button>
 
      <MUISlider client:signal={hydrateReact}></MUISlider>
    </>
  );
});これにより、ハイドレーションのカスタム戦略を効果的に実装できます。
client:event
指定されたDOMイベントがディスパッチされたときに、コンポーネントはすぐにハイドレートされます。
<MUISlider client:event="click"></MUISlider>client:only
trueの場合、コンポーネントはSSRでは実行されず、ブラウザでのみ実行されます。
<MUISlider client:only></MUISlider>Reactイベントのリスニング
Reactのイベントは、関数をコンポーネントのプロパティとして渡すことによって処理されます。例:
// React code (won't work in Qwik)
 
import { Slider } from '@mui/material';
 
<Slider onChange={() => console.log('value changed')}></Slider>;qwikify()関数は、これをQwikコンポーネントに変換し、ReactイベントをQwikのQRLとしても公開します。
import { Slider } from '@mui/material';
import { qwikify$ } from '@builder.io/qwik-react';
const MUISlider = qwikify$(Slider);
 
<MUISlider client:visible onChange$={() => console.log('value changed')} />;コンポーネントをすぐにハイドレートするために
client:visibleプロパティを使用していることに注意してください。そうしないと、コンポーネントがインタラクティブにならず、イベントがディスパッチされることはありません。
ホスト要素
Reactコンポーネントをqwikify$()でラップすると、内部的には、次のような新しいDOM要素が作成されます。
<qwik-react>
  <button class="MUI-button"></button>
</qwik-react>ラッパー要素のタグ名は
tagNameで構成可能であることに注意してください。例:qwikify$(ReactCmp, { tagName: 'my-react' })。
ハイドレーションなしでDOMイベントをリッスンする
ホスト要素はReactの一部ではないため、イベントをリッスンするためにハイドレーションは必要ありません。カスタム属性とイベントをホスト要素に追加するには、JSXプロパティでhost:プレフィックスを使用できます。例:
<MUIButton
  host:onClick$={() => {
    console.log('click a react component without hydration!!');
  }}
/>これにより、Reactコードを1バイトもダウンロードせずに、MUIボタンのクリックに応答することを効果的に可能にします。
🧑💻ハッピーハッキング!










