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.json
dependencies
@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');
が実行されないことにも注意してください。
host:
リスナー
4. 前の例では、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
アイランドは決してハイドレートされないことに注意してください。したがって、この変更により、サーバー側のみでレンダリングされ、ブラウザでハイドレートする必要がないアイランドを持つことができ、ユーザーの時間を節約できます。
children
の投影
5. 一般的なユースケースは、コンテンツの子をコンポーネントに渡すことです。これは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ボタンのクリックに応答することを効果的に可能にします。
🧑💻ハッピーハッキング!