SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Remixを通じてWebを学ぶ

React Routerを元に誕生した「Remix」──ルーティングに関するAPIを解説!

Remixを通じてWebを学ぶ 第5回

  • X ポスト
  • このエントリーをはてなブックマークに追加

複雑なルートを定義する

 前項で1階層分のパスを定義する方法は分かりました。しかし、実際のWebサイト制作では、もう少し複雑なURLの構築を求められます。2つ以上の階層を持つパスを定義したり、表示するデータのIDをパスとして使用したいこともあるでしょう。

 Remixはそういったケースにも対応しているので、やり方を見ていきましょう。

パスの階層を定義する

 まずは、深い階層のパスを作る方法です。Remixでは、深い階層のパスを定義したい場合、ファイル名をドットで区切ることで表現します。公式ドキュメントではDot Delimitersと呼ばれる表記方法です。直訳すると「ドット区切り」なので、本記事でもドット区切りと表記します。

 例えば、/about/me というパスを定義したい場合は、app/routes/about.me.jsx というファイル名にすれば実現できます。また、階層はいくつ区切っても大丈夫なので、app/routes/foo.bar.buz.jsx を定義して /foo/bar/buz というパスを作ってもOKです。

 なお、このドット区切りのパス表現でも _index という命名は有効なので、前述した /top20 というパスは app/routes/top20._index.jsx という名前でも表現できます。

パスでパラメータを表現する

 次は、URLのパスにパラメータを持つ方法について解説します。/top20/1234512345 の部分のように、データのIDをパスに含めたいことがありますよね。そういった場合は、パス名の先頭にドル記号 $ を付けて、パラメータ名を定義できます。

 具体的には、app/routes/top20.$id.jsx のようなファイル名を定義すると、/top20/12345 のパスでアクセスできるようになります。$id の部分は、リスト2のように loader の引数で取得できます。

[リスト2]app/routes/top20.$id.jsx
export const loader = async ({ params }) => {
  const { id } = params; // (1)
  // データを取得して画面へ送る処理
};

 今回はファイル名が $id だったので、(1)で params から取り出したプロパティも id になっていますが、パラメータ名は自由なので、$item_id$key にしてもOKです。app/routes/user.$user_id.$item_id のように、複数のパラメータを指定することもできます。

 また、loader だけではなく action の引数でも同様にパラメータを利用できるので、編集画面での送信処理にもパラメータを活用できます。

レイアウトとしてのルートを定義する

 Remixの特徴的な機能として、Nested Routes(ネステッドルーツ)というルーティング方法があります。Webサイトのよくあるページ構成として、ヘッダー部とメニュー部とコンテンツ部が分かれていることがありますよね。ページ遷移したときに大きく切り替わるのはコンテンツ部だけしかない、ということはあります(図3)。

図3:一部だけしか変わる必要がないページ遷移
図3:一部だけしか変わる必要がないページ遷移

 そういったケースのために、コンテンツ部とそれ以外を別々のルートとして定義して、ページ遷移するときはシングルページアプリケーションとして、本当にコンテンツ部だけ切り替えられるようにしよう、というコンセプトがNested Routesです。

 Nested Routesが成立するための条件は2つあります。

  1. ドット区切りによるパスを定義した状態で、上位のパス名と名前が一致するファイル名を定義して、「外側」のコンポーネントを定義する
  2. コンポーネント内の、「内側」のコンテンツを表示したい場所に <Outlet /> を配置する

 具体例を挙げてみましょう。第3回で作成したHacker News Viewerを再現する形で、次の3つのファイルを使ってNested Routesを構成してみましょう。

  • app/routes/top20.jsx:ヘッダーとメニューが定義された外側のコンポーネント
  • app/routes/top20._index.jsx:初期ページ
  • app/routes/top20.$id.jsx:個別の記事ページ

 まずは、外側のコンポーネントである app/routes/top20.jsx を作成します(リスト3)。

[リスト3]app/routes/top20.jsx
// (略)
export default function Top20Route() {
  // loaderで取得済みのデータを取り出す
  const data = useLoaderData();

  return (
    <div>
      <header>
        <h1>Hacker News Viewer</h1>
      </header>
      <div id="container">
        <div id="sidebar">
          <h2>Top 20</h2>
          <nav>
            <ul>
              {data.map((item) => (
                <li key={item.id}>
                  <Link to={/top20/${item.id}}>{item.title}</Link>
                </li>
              ))}
            </ul>
          </nav>
        </div>
        <main>
          <Outlet />{/ (1) /}
        </main>
      </div>
    </div>
  );
}

 省略部分についてはサンプルコードを参照してください。

 <main> 要素の中がコンテンツ部になるようにスタイルを組んであるので、(1)で <main> 要素の中に <Outlet /> を配置して、コンテンツが表示できるようにしました。

 これだけではページとして成立しないので、<Outlet /> に表示できるものも作らないといけません。簡単に app/routes/top20._index.jsx を作成してみましょう(リスト4)。

[リスト4]app/routes/top20._index.jsx
import stylesUrl from '~/style/article.css';

export const links = () => {
  return [{ rel: "stylesheet", href: stylesUrl }];
};

export default function Top20IndexRoute() {
  return (
    <article>
      <p>ここに記事が表示されます。</p>
      <p>左のメニューから記事を選択してください。</p>
    </article>
  )
}

 当たり障りなくテキストが書いてあるだけのコンポーネントです。では、このリスト4に該当するページである http://localhost:3000/top20 を開いてみましょう(図4)。

図4:http://localhost:3000/top20
図4:http://localhost:3000/top20

 外側にリスト3のヘッダーとメニュー、内側にリスト4のテキストが表示され、両者が <Outlet /> を通じて、上手く組み合わさっています。

 それでは、個別の記事ページである app/routes/top20.$id.jsx も作りましょう(リスト5)。

[リスト5]app/routes/top20.$id.jsx
// (略)
export default function Top20IdRoute() {
  const { item, kids } = useLoaderData();

  return (
    <article>
      <h1>{item.title}</h1>
      <p>by {item.by} on {new Date(item.time * 1000).toLocaleString()}</p>
      <p>{item.text}</p>
      <p><a href={item.url}>{item.url}</a></p>

      <h2>Comments</h2>
      {kids.map((kidsItem) => (
        <div key={kidsItem.id}>
          <h3>by: {kidsItem.by}</h3>
          <p>{kidsItem.text}</p>
          <p>{new Date(kidsItem.time * 1000).toLocaleString()}</p>
          <hr />
        </div>
      ))}
    </article>
  );
}

 こちらも省略部分についてはサンプルコードを参照してください。

 タイトルやコメントが書いてあるページです。作成したら、メニューの記事をどれか選んでみてください(図5)。

図5:記事ページ
図5:記事ページ

 記事が表示されました。メニューの記事名をクリックしていくと、内側のコンポーネントだけが切り替わっていきます。

 外側のコンポーネントとコンテンツを別々に実装することができました。外側のコンポーネントのことは、特別に「レイアウト」と呼ぶこともあり、Nested Routesはレイアウト機能とも呼ばれます。

 ちなみに、app/root.jsx はすべてのルートの外側に被さるレイアウトの一種です。

URLをネストさせずにレイアウトだけ使いたい

 レイアウトには、もう少し柔軟な使い方もあります。ログイン画面やユーザー登録の画面に、専用のレイアウトを用意したいことってありますよね。ここまでの知識で素直に実装すると、次のようなファイルを用意すればいいように思えます。

  • app/routes/auth.login.jsx:ログイン画面
  • app/routes/auth.register.jsx:ユーザー登録画面
  • app/routes/auth.jsx:上記2画面のレイアウト

 もちろんこれは /auth/login のように、/auth/ という階層を含むURLでアクセスすることになります。ですが、URLの設計として /login/register というルートパスの直下に認証系のパスを置きたいこともありますよね。

 app/routes/ フォルダの直下に login.jsxregister.jsx を配置して、ルートレイアウトである app/root.jsx を編集する手もないわけではありませんが、認証と関係の無いルートにまで影響が出かねないのは、いいことではなさそうです。

 そんなときは、パス名の先頭にアンダースコアをつけてみましょう。そのパスはレイアウトだけで利用され、URLのパスとしては利用されなくなります。つまり、今回の例で言うと、ファイル名を次のようにすると上手くいきます。

  • app/routes/_auth.login.jsx:ログイン画面
  • app/routes/_auth.register.jsx:ユーザー登録画面
  • app/routes/_auth.jsx:上記2画面のレイアウト

 これで /login/register だけに _auth.jsx で定義したレイアウトが適用されるようになります。

まとめ

 今回はルーティングについて解説しました。公式ドキュメントには、他にもルーティングの小技が紹介されているので、困った時には目を通してみると良いかもしれません。

 次回は、ルートに設定できる内容について詳細に解説します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Remixを通じてWebを学ぶ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/18573 2023/11/16 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング