ルーティング
PrefXは、urlユーティリティを通じて、軽量でシグナルベースのクライアントサイドルーティングメカニズムを提供します。ルーターコンポーネントをインポートする必要はありません。ブラウザの位置情報と同期するリアクティブなReadonlySignal<URL>が、Navigation APIを介して提供されます。
urlシグナル
すべてのPrefXコンポーネントは、2番目の引数(utils)にurlを受け取ります。
| プロパティ | タイプ | 説明 |
|----------|------|-------------| | url | ReadonlySignal<URL> | .value が現在の URL オブジェクトであるリアクティブシグナル。ユーザーが同一オリジンのページに移動するたびに変更されます。|
タイプ署名
type PreffXUtils = {
url: ReadonlySignal<URL>;
// ...
};基本的なルーティング
url.value を使用すると、現在のパス名、検索パラメータ、ハッシュ、またはその他の URL プロパティを読み取ることができます。computed と組み合わせることで、ビューを動的に切り替えることができます。
import type { PC } from 'preffx';
const HomePage: PC = () => <div>Home page</div>;
const ContactsPage: PC = () => <div>Contacts page</div>;
const NotFound: PC = () => <div>Page not found</div>;
export const App: PC = (_, { computed, url }) => {
const page = computed(() => {
switch (url.value.pathname) {
case '/':
return <HomePage />;
case '/contacts':
return <ContactsPage />;
default:
return <NotFound />;
}
});
return (
<div>
<nav>
<a href="/">Home</a>
<a href="/contacts">Contacts</a>
</nav>
<main>{page}</main>
</div>
);
};仕組み
- 通常通り
<a href="/contacts">リンクをレンダリングします。 - ユーザーがリンクをクリックすると、ブラウザは
navigateイベントを発信します。 - PreffX はこのイベントをインターセプトし(
event.preventDefault()を呼び出し)、内部のurlシグナルを更新します。 url.valueに依存するcomputedはすべて再評価され、DOM がリアクティブに更新されます。
ナビゲーションリンク
標準の<a>要素にhref属性を付けて使用してください。PrefXが自動的に処理するため、特別な<Link>コンポーネントは不要です。
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contacts">Contacts</a>
<a href="/products?category=books&sort=asc">Books</a>
</nav>PreffXのインターセプターは、少なくとも1つのコンポーネントがurlシグナルを使用している場合にのみアクティブになります(内部のURL_WATCHERSカウンターで監視されます)。つまり、以下のようになります。
- コンポーネントが
urlを読み取っていない場合、<a>リンクをクリックしても正常に動作します(ページ全体のナビゲーション)。 - コンポーネントが
url.valueにアクセスするとすぐに、ナビゲーションがインターセプトされ、クライアント側で処理されます。
URLの各要素の読み取り
urlはReadonlySignal<URL>であるため、ネイティブのURLオブジェクトの任意のプロパティを読み取ることができます。
const CurrentRoute: PC = (_, { url }) => {
return (
<div>
<p>Pathname: {url.value.pathname}</p>
<p>Search: {url.value.search}</p>
<p>Hash: {url.value.hash}</p>
<p>Host: {url.value.host}</p>
<p>Origin: {url.value.origin}</p>
</div>
);
};これらはすべてリアクティブです。いずれかの値が変更されると、関連するシグナルの再計算がトリガーされます。
ルートパラメータ(クエリ文字列)
computed 内で url.value.searchParams からクエリパラメータを解析します。
import type { PC } from 'preffx';
const ProductList: PC = (_, { computed, url }) => {
const category = computed(() =>
url.value.searchParams.get('category') ?? 'all'
);
const sort = computed(() =>
url.value.searchParams.get('sort') ?? 'name'
);
return (
<div>
<p>Category: {category}</p>
<p>Sort by: {sort}</p>
<a href="/products?category=books&sort=asc">Books (asc)</a>
<a href="/products?category=books&sort=desc">Books (desc)</a>
</div>
);
};動的セグメント(パスパラメータ)
urlは完全なURLオブジェクトを返すため、パスセグメントを手動で解析できます。
import type { PC } from 'preffx';
const UserProfile: PC = (_, { computed, url }) => {
const segments = computed(() => url.value.pathname.split('/').filter(Boolean));
// pathname = "/users/42" → segments = ["users", "42"]
const userId = computed(() => segments.value[1]);
return <div>User ID: {userId}</div>;
};より体系的なアプローチとして、シンプルなルートマッチング機能を構築します。
import type { PC } from 'preffx';
// Simple route pattern → params extractor
function match(pattern: string, pathname: string): Record<string, string> | null {
const patternParts = pattern.split('/').filter(Boolean);
const pathParts = pathname.split('/').filter(Boolean);
if (patternParts.length !== pathParts.length) return null;
const params: Record<string, string> = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
params[patternParts[i].slice(1)] = pathParts[i];
} else if (patternParts[i] !== pathParts[i]) {
return null;
}
}
return params;
}
const RouteExample: PC = (_, { computed, url }) => {
const route = computed(() => {
const { pathname } = url.value;
const params = match('/users/:id', pathname);
if (params) return <div>User {params.id}</div>;
const postParams = match('/posts/:postId', pathname);
if (postParams) return <div>Post {postParams.postId}</div>;
return <div>Not found</div>;
});
return (
<div>
<nav>
<a href="/users/42">User 42</a>
<a href="/users/7">User 7</a>
<a href="/posts/abc">Post abc</a>
</nav>
<main>{route}</main>
</div>
);
};クロスオリジンナビゲーション
ナビゲーションインターセプターは、同一オリジン間のナビゲーションにのみ適用されます。クロスオリジンリンク(異なるプロトコル、ホスト、またはポート)は影響を受けず、通常どおりページ全体の読み込みが行われます。
<a href="https://example.com">External link</a> <!-- full navigation -->
<a href="/internal">Internal link</a> <!-- client‑side navigation -->ブラウザ対応状況
ルーティング機能はNavigation API(globalThis.navigation)を利用しています。
2026年1月以降、この機能は最新のデバイスとブラウザバージョンで動作します。古いデバイスやブラウザでは動作しない場合があります。