ライフサイクルイベント
アダプターとdefineMicroAppは、マイクロアプリがマウント、アンマウント、または失敗したときにページに通知します。DOMイベントをリッスンする、whenMounted(name)を待機する、isMounted(name)で確認する、のいずれかの方法を使えます。DOMをポーリングしたり、__vue_app__のようなフレームワークの内部を読み取ったりする代わりに、アイランドがインタラクティブになった時点でアニメーションを開始する、スケルトンを非表示にする、エラーを報告する、といった用途に使ってください。
イベント
| DOMイベント | ディスパッチされるタイミング |
|---|---|
tuvix:mounted | アプリがレンダリングまたはハイドレートされ、そのフレームワークのマウントコールバックが実行された後。 |
tuvix:unmounted | アプリが破棄され、コンテナが空になった後。 |
tuvix:error | mountまたはunmountがエラーをスローしたとき、またはReactアプリがマウント後のレンダリング中にエラーをスローしたとき。 |
各イベントは、アプリのコンテナ上でディスパッチされるCustomEvent<LifecycleDetail>です。バブリングし、composedであるため、Shadow Rootの内部からでもdocument上のリスナーが受け取ります。コンテナがDOMに存在しなくなった場合(モジュールがunmountedを通知する前にReactがアイランドを削除した場合)、イベントは代わりにdocument上でディスパッチされ、detail.containerは引き続きその要素を指します。
import type { LifecycleDetail } from '@tuvix.js/loader';
const detail: LifecycleDetail = {
name: 'footer', // the app name
container: document.body, // the element the app rendered into
hydrated: true, // true when the adapter adopted server markup
error: undefined, // the thrown value, on 'error' only
};onLifecycleでリッスンする
import { onLifecycle } from 'tuvix.js';
const stopMounted = onLifecycle('mounted', ({ name, hydrated }) => {
console.log(`${name} is interactive (hydrated: ${hydrated})`);
});
const stopErrors = onLifecycle('error', ({ name, error }) => {
console.error(`${name} failed`, error);
});
// later
stopMounted();
stopErrors();onLifecycle(type, handler, target = document)は、リスナーを削除する関数を返します。1つの領域だけを対象にするには、targetとしてコンテナを渡します。通常のaddEventListener('tuvix:mounted', ...)も使えます。
whenMountedで待機する
import { whenMounted } from 'tuvix.js';
const controller = new AbortController();
setTimeout(() => controller.abort(new Error('footer did not mount in 5 s')), 5000);
const { container } = await whenMounted('footer', { signal: controller.signal });
container.classList.add('is-ready');- アプリがすでにマウントされている場合は即座に解決するため、コードの実行とマウントの順序は問題になりません。
- マウントが失敗してもリジェクトしません。タイムアウト付きの
signalを渡すか、tuvix:errorもリッスンしてください。 - シグナルが中断されると、Promiseはシグナルの理由でリジェクトします。
isMountedで確認する
import { isMounted } from 'tuvix.js';
if (isMounted('footer')) {
console.log('footer is on the page');
}フレームワークの検出を置き換える
ライフサイクルイベントがない場合、ホストはフレームワークのプライベートプロパティをポーリングするなどして、アイランドの準備ができたタイミングを推測する必要があります:
// Breaks on Svelte, Angular and any Vue release that renames the property.
const timer = setInterval(() => {
if ((container as any).__vue_app__) {
clearInterval(timer);
startReveal(container);
}
}, 50);ライフサイクルイベントを使えば、1つのリスナーですべてのアダプターに対応できます:
import { onLifecycle } from 'tuvix.js';
declare function startReveal(container: HTMLElement): void;
onLifecycle('mounted', ({ name, container }) => {
if (name === 'footer') startReveal(container);
});イベントをディスパッチするもの
| ソース | ディスパッチするイベント |
|---|---|
createReactMicroApp、createVueMicroApp、createSvelteMicroApp、createAngularMicroApp(およびSsrバリアント) | 3つすべて |
TuvixReactApp | 3つすべて |
TuvixIsland | 自身はディスパッチしません。ホストするモジュールがディスパッチします |
defineMicroApp | 3つすべて。ssr: trueの場合、mountの前にコンテナがマークアップを保持していればhydratedはtrueです |
通常の{ mount, unmount }オブジェクト | なし(emitLifecycleを呼び出さない限り) |
| オーケストレーター | なし。代わりにイベントバス上でtuvix:app:*イベントを発行します |
defineMicroAppは、mountが解決した後にtuvix:mountedを、unmountが解決した後にtuvix:unmountedをディスパッチし、どちらかがエラーをスローしたときはtuvix:errorをディスパッチしてから再スローします。mountがまだ実行中のコンテナに対するunmountは、その完了を待ちます:
import { defineMicroApp } from '@tuvix.js/core';
export default defineMicroApp({
name: 'clock',
ssr: true, // the server rendered the time into the container
mount({ container }) {
container.textContent = new Date().toISOString();
},
unmount({ container }) {
container.replaceChildren();
},
});通常のオブジェクトとして書かれたモジュールは、イベントを自分でディスパッチできます:
import { emitLifecycle, type MicroAppModule } from '@tuvix.js/loader';
export const clock: MicroAppModule = {
mount({ container }) {
container.textContent = new Date().toISOString();
emitLifecycle('mounted', { name: 'clock', container, hydrated: false });
},
unmount({ container }) {
container.replaceChildren();
emitLifecycle('unmounted', { name: 'clock', container, hydrated: false });
},
};エッジケース
- 1つのモジュールを複数のコンテナにマウントできます。
isMounted(name)はいずれかがマウントされている間trueのままで、whenMounted(name)はまだマウントされているうち最も早いコンテナで解決します。それらを区別する必要がある場合は、配置ごとに別の名前を付けてください。 - 切り離されたコンテナの
tuvix:unmountedイベントは、documentのリスナーに1回届きます。targetにコンテナを指定したonLifecycleは、イベントがその要素を通過しなくなるため、これを受け取りません。 - 状態は
window.__TUVIX_RUNTIME__に保持されるため、別々にバンドルされた@tuvix.js/loaderのコピー間で共有されます。 - サーバー上にはDOMもイベントもありません。サーバーで
whenMountedを呼び出さないでください。解決されることはありません。
できないこと
- 読み込みの進捗は通知しません。
bootstrapping、mountingなどはオーケストレーターのonStatusChangeで扱います。 - 再試行や復旧は行いません。オーケストレーターの
fallback、またはTuvixIslandとTuvixReactAppのfallbackを使ってください。