Skip to content

ルーティング

PrefXは、urlユーティリティを通じて、軽量でシグナルベースのクライアントサイドルーティングメカニズムを提供します。ルーターコンポーネントをインポートする必要はありません。ブラウザの位置情報と同期するリアクティブなReadonlySignal<URL>が、Navigation APIを介して提供されます。

urlシグナル

すべてのPrefXコンポーネントは、2番目の引数(utils)にurlを受け取ります。

| プロパティ | タイプ | 説明 |

|----------|------|-------------| | url | ReadonlySignal<URL> | .value が現在の URL オブジェクトであるリアクティブシグナル。ユーザーが同一オリジンのページに移動するたびに変更されます。|

タイプ署名

typescript
type PreffXUtils = {
    url: ReadonlySignal<URL>;
    // ...
};

基本的なルーティング

url.value を使用すると、現在のパス名、検索パラメータ、ハッシュ、またはその他の URL プロパティを読み取ることができます。computed と組み合わせることで、ビューを動的に切り替えることができます。

tsx
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>
    );
};

仕組み

  1. 通常通り <a href="/contacts"> リンクをレンダリングします。
  2. ユーザーがリンクをクリックすると、ブラウザは navigate イベントを発信します。
  3. PreffX はこのイベントをインターセプトし(event.preventDefault() を呼び出し)、内部の url シグナルを更新します。
  4. url.value に依存する computed はすべて再評価され、DOM がリアクティブに更新されます。

ナビゲーションリンク

標準の<a>要素にhref属性を付けて使用してください。PrefXが自動的に処理するため、特別な<Link>コンポーネントは不要です

tsx
<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の各要素の読み取り

urlReadonlySignal<URL>であるため、ネイティブのURLオブジェクトの任意のプロパティを読み取ることができます。

tsx
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 からクエリパラメータを解析します。

tsx
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オブジェクトを返すため、パスセグメントを手動で解析できます。

tsx
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>;
};

より体系的なアプローチとして、シンプルなルートマッチング機能を構築します。

tsx
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>
    );
};

クロスオリジンナビゲーション

ナビゲーションインターセプターは、同一オリジン間のナビゲーションにのみ適用されます。クロスオリジンリンク(異なるプロトコル、ホスト、またはポート)は影響を受けず、通常どおりページ全体の読み込みが行われます。

tsx
<a href="https://example.com">External link</a>  <!-- full navigation -->
<a href="/internal">Internal link</a>             <!-- client‑side navigation -->

ブラウザ対応状況

ルーティング機能はNavigation APIglobalThis.navigation)を利用しています。

2026年1月以降、この機能は最新のデバイスとブラウザバージョンで動作します。古いデバイスやブラウザでは動作しない場合があります。

Apache License 2.0に基づいて公開されています。