Reactで使う
@tuvix.js/reactには3つの役割があります。createReactMicroAppはReactコンポーネントを、どのシェルからでもマウントできるマイクロアプリに変換します。TuvixReactAppはReactのホストページ内でReactのマイクロアプリをサーバーレンダリング付きでレンダリングします。TuvixIslandはVue、Svelte、Angular、React、または手書きのマイクロアプリをReactのページ内でホストします。
React 18または19が必要です。
インストール
pnpm add @tuvix.js/react react react-domコンポーネントをマイクロアプリにする
// src/main.tsx
import { createReactMicroApp } from '@tuvix.js/react';
interface ProfileProps {
userId: string;
theme: 'light' | 'dark';
}
function Profile({ userId, theme }: ProfileProps) {
return <div className={`profile ${theme}`}>User {userId}</div>;
}
export default createReactMicroApp({ name: 'profile', App: Profile });このモジュールは次のように動作します。
nameで自身を登録するため、URLエントリーから見つけられます。- コンテナごとに1つのReactルートを作成するため、1つのモジュールを複数の場所にマウントできます。
- Reactが最初のレンダリングをコミットした後に
mountを解決し、コンテナでtuvix:mountedをディスパッチします。 - シェルのプロパティを
Appのpropsとして渡します。Appは必須のpropsを宣言できますが、アダプターは実行時にそれを検査しません。
| オプション | 型 | 説明 |
|---|---|---|
name | string | 必須。登録したアプリ名と一致する必要があります。 |
App | ComponentType<P> | 必須。ルートコンポーネント。 |
bootstrap | () => void | Promise<void> | 最初のマウントの前に1回実行されます。 |
ssr | boolean | コンテナにサーバーのマークアップがある場合にハイドレートします。createSsrReactMicroAppはこれを設定します。 |
プロパティと更新
import { createOrchestrator } from '@tuvix.js/core';
const orchestrator = createOrchestrator();
await orchestrator.updateAppProps('profile', { theme: 'light' });update({ props, container })は、そのコンテナのルートをマージ後のプロパティで再レンダリングします。コンポーネントの状態は維持されます。containerを指定しない場合は、モジュールのすべてのルートを再レンダリングします。このモジュールがすでにレンダリングしたコンテナに対してmountを呼び出すと、2つ目のルートを作成せずに再レンダリングします。
エラー
アダプターはAppをエラーバウンダリーでラップします。
- 最初のレンダリング中に例外が発生すると、
mountをrejectし、コンテナを空にしてtuvix:errorをディスパッチします。その後、オーケストレーターはアプリのfallbackのHTMLを表示します。 - それ以降に例外が発生すると、アプリを空白にして
tuvix:errorをディスパッチします。ページの残りの部分は動作し続けます。
サーバーレンダリングされるReactアプリ
ホストページがReactで、サーバーでレンダリングされる場合(TanStack Start、Next.js、Remix、または独自のrenderToString)は、TuvixReactAppでマイクロアプリをインラインでレンダリングします。
import { TuvixReactApp } from '@tuvix.js/react';
function LegalPage({ page }: { page: 'privacy' | 'terms' }) {
return <article>{page}</article>;
}
export function PrivacyRoute() {
return (
<TuvixReactApp
name="legal"
App={LegalPage}
page="privacy"
fallback={<p>This section failed to load.</p>}
/>
);
}Appを囲む<div data-tuvix-app="legal">をレンダリングするため、HTMLはホスト自身のサーバーレンダリングから出力されます。name、App、fallback以外のすべてのプロパティはAppに渡されます。Appは専用の<Suspense fallback>の中に置かれます。サーバーで例外が発生した場合、Reactはこのアプリについてのみフォールバックを送信し、クライアントでアプリを再試行します。- クライアントでは、エラーバウンダリーが
fallbackを表示し、tuvix:errorをディスパッチします。 - 最初のコミットの後、サーバーのHTMLをハイドレートした場合は
hydrated: true付きでtuvix:mountedをディスパッチし、ツリーから外れるときにtuvix:unmountedをディスパッチします。
別のバンドルがそのマークアップをハイドレートする必要がある場合(例:@tuvix.js/serverが組み立てたページ)は、同じコンポーネントをcreateSsrReactMicroAppでエクスポートします。同じSuspenseツリーをレンダリングするため、ハイドレーションが一致します。
import { createSsrReactMicroApp } from '@tuvix.js/react';
function LegalPage({ page }: { page: 'privacy' | 'terms' }) {
return <article>{page}</article>;
}
export default createSsrReactMicroApp({ name: 'legal', App: LegalPage });ssr: trueの場合、空白文字やコメントしか含まないコンテナは、ハイドレーションではなく新規にレンダリングされます。
TuvixIslandで他のマイクロアプリをホストする
TuvixIslandは任意のマイクロアプリモジュールをReactツリー内にマウントします。
import { TuvixIsland } from '@tuvix.js/react';
import { createSsrVueMicroApp } from '@tuvix.js/vue';
import Newsletter from './Newsletter.vue';
const newsletter = createSsrVueMicroApp({ name: 'newsletter', App: Newsletter });
export function Footer({ newsletterHtml }: { newsletterHtml?: string }) {
return (
<TuvixIsland
name="newsletter"
app={newsletter}
props={{ list: 'weekly' }}
ssrHtml={newsletterHtml}
fallback={<p>Subscribe at example.com/newsletter</p>}
/>
);
}| プロパティ | 説明 |
|---|---|
name | data-tuvix-appに書き込まれ、mountに渡されます。 |
app | マウントするモジュール。 |
props | レンダリングごとに浅く比較されます。変更があるとapp.updateを呼び出します。モジュールにupdateがない場合はアンマウントしてからマウントします。 |
ssrHtml | アイランドのサーバーHTML。サーバーでのみ必要です。 |
fallback | mountまたはupdateがrejectした場合に、アイランドの代わりにレンダリングされます。 |
as、className | ラッパーのタグ(デフォルトはdiv)とクラス。 |
Reactは最初のレンダリングの後、アイランドの内部HTMLを書き換えません。そのため親が再レンダリングされても、モジュールがアタッチしたDOMとリスナーは維持されます。nameまたはappが変わると再マウントし、アイランドを削除するとapp.unmountを呼び出します。マウント、更新、アンマウントは順番に実行されるため、mountの実行中に届いたアンマウントはその完了を待ちます。
TuvixIslandはURLからモジュールを読み込みません。モジュールをインポートするか、URLエントリーはオーケストレーターに管理させます。サーバーの完全なセットアップはSSRアイランドにあります。
フック
useTuvixBus
マウント時に購読し、アンマウント時に購読を解除します。最新のhandlerをrefを通じて読み取るため、インラインのハンドラーでもレンダリングごとに再購読しません。
import { useState } from 'react';
import { getGlobalBus } from '@tuvix.js/event-bus';
import { useTuvixBus } from '@tuvix.js/react';
const bus = getGlobalBus();
export function CartBadge() {
const [count, setCount] = useState(0);
useTuvixBus<{ itemCount: number }>(bus, 'cart:updated', ({ itemCount }) => setCount(itemCount));
return <span className="badge">{count}</span>;
}useTuvixProps
useTuvixProps(initialProps, bus?, updateEvent = 'tuvix:props:update')は、updateEventで発行されたすべてのペイロードをマージする状態を返します。Tuvix.jsにこのイベントを発行する処理はありません。updateAppPropsではなくバス経由でプロパティを渡したい場合は、自分で発行します。
import { getGlobalBus } from '@tuvix.js/event-bus';
import { useTuvixProps } from '@tuvix.js/react';
const bus = getGlobalBus();
export function Theme() {
const props = useTuvixProps({ theme: 'light' }, bus);
return <p>Theme: {props.theme}</p>;
}
bus.emit('tuvix:props:update', { theme: 'dark' });アダプターが行わないこと
- マイクロアプリ間で1つのReactルートを共有しません。各アプリ、各コンテナがそれぞれ専用のルートを持ちます。
- Reactの重複を排除しません。別々にバンドルされた2つのReactマイクロアプリは、Reactのコピーを2つ配布します。
- ホストページの
<head>はレンダリングしません。タイトルとmetaタグはホストが担います。
API
| エクスポート | 用途 |
|---|---|
createReactMicroApp({ name, App, bootstrap?, ssr? }) | Reactマイクロアプリのモジュール |
createSsrReactMicroApp({ name, App, bootstrap? }) | ssr: trueを設定した同等のもの |
TuvixReactApp | Reactホスト内のインラインReactマイクロアプリ(SSR対応) |
TuvixIsland | 任意のマイクロアプリモジュールをReactツリー内でホストする(SSR対応) |
useTuvixBus(bus, event, handler) | コンポーネントに紐づいたイベントバスの購読 |
useTuvixProps(initial, bus?, event?) | バスのイベントからマージされるプロパティ |