Deno Fresh のパーシャル

作成日
更新日

Fresh のパーシャル

パーシャル

パーシャル(Partials)を使用すると、ページの特定の領域をサーバーからの新しいコンテンツで更新し、ブラウザ全体のリロードを発生させずに処理できます。これにより、ウェブサイトはアプリのように感じられ、必要な部分のみが更新されます。

パーシャルの有効化

パーシャルは、HTML要素にf-client-nav属性を追加し、ページ内の1つ以上の領域を<Partial name="my-partial">コンポーネントでラップすることで有効になります。

最も簡単な方法は、routes/_app.tsxに以下の変更を加えて、すべてのページでパーシャルを有効にすることです。

routes/_app.tsx
import { PageProps } from "$fresh/server.ts";
import { Partial } from "$fresh/runtime.ts";

export default function App({ Component }: PageProps) {
  return (
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My Fresh app</title>
      </head>
      <body f-client-nav>
        <Partial name="body">
          <Component />
        </Partial>
      </body>
    </html>
  );
}

f-client-nav属性を追加することで、<body>タグの下にあるすべての要素でパーシャルが有効になります。ページの特定の領域をパーシャルとしてマークするには、それをユニークな名前を持つ<Partial>コンポーネントでラップします。

内部的には、ユーザーが<a>タグをクリックすると、Freshが新しいページをフェッチし、HTMLレスポンスから該当するコンテンツのみを抽出します。対応するパーシャル領域が見つかると、その中身が更新されます。

注意点

  • <Partial>コンポーネントのnameプロパティは、パーシャルの中でユニークである必要があります。Freshはこれを使って、レスポンスのどの部分を現在のページに適用するかを判断します。
  • f-client-nav={false}を渡すことで、特定の要素以下のすべてのクライアントサイドナビゲーションが無効になります。

パーシャルリクエストの最適化

デフォルトでは、f-client-navが設定されると、Freshは次のページ全体をフェッチし、レスポンスの該当部分のみを抽出します。しかし、特定の部分のみをレンダリングすることで、このパターンをさらに最適化できます。これを行うには、リンクにf-partial属性を追加します。

- <a href="/docs/routes">Routes</a>
+ <a href="/docs/routes" f-partial="/partials/docs/routes">Routes</a>

f-partial属性がある場合、Freshhref属性で定義されたページURLにナビゲートしますが、f-partialで指定されたURLから更新されたUIをフェッチします。これにより、必要なコンテンツだけを提供する高い効率性を持つルートを作成できます。

典型的なドキュメントページのレイアウトを例に考えます。このようなページでは、メインコンテンツエリアとサイドバーに複数のリンクがあり、ドキュメントのページを切り替えることがよくあります(ここでは緑色で示されています)。

Deno Fresh のコンセプトについて④-20240909061459636

このようなページのコード(スタイリングを除く)は次のようになります:

routes/docs/[id].tsx
export default defineRoute(async (req, ctx) => {
  const content = await loadContent(ctx.params.id);
  return (
    <div>
      <aside>
        <a href="/docs/page1">Page 1</a>
        <a href="/docs/page2">Page 2</a>
      </aside>
      <Partial name="docs-content">
        {content}
      </Partial>
    </div>
  );
});

外側のレイアウトやサイドバーをレンダリングせず、コンテンツだけをレンダリングする最適なルートは次のようになります:

routes/doc/[id].tsx
import { defineRoute, RouteConfig } from "$fresh/server.ts";
import { Partial } from "$fresh/runtime.ts";

// コンテンツのみをレンダリングするために、
// `_app.tsx`テンプレートや継承されたレイアウトを無効化します
export const config: RouteConfig = {
  skipAppWrapper: true,
  skipInheritedLayouts: true,
};

export default defineRoute(async (req, ctx) => {
  const content = await loadContent(ctx.params.id);

  // 新しいコンテンツのみをレンダリング
  return (
    <Partial name="docs-content">
      {content}
    </Partial>
  );
});

f-partial属性を追加することで、Freshは新しく追加された/partials/docs/[id].tsxルートからコンテンツをフェッチします。

  <aside>
-   <a href="/docs/page1">Page 1</a>
-   <a href="/docs/page2">Page 2</a>
+   <a href="/docs/page1" f-partial="/partials/docs/page1">Page 1</a>
+   <a href="/docs/page2" f-partial="/partials/docs/page2">Page 2</a>
  </aside>

この設定により、2つのリンクのいずれかをクリックすると、Freshは新しいページに移動し、最適化されたパーシャルルートでレンダリングされたコンテンツだけをロードします。

なぜか、1度404ページが表示されてから、リロードしたらページが表示される。

注意

f-partialは現在、<a><button>、および<form>要素にスコープされています。将来的に他の要素にも拡張される可能性があります。

複数のパーシャルの同時送信

Freshのパーシャルの便利な点は、1つのレスポンスで複数のパーシャルを返すことができることです。これにより、1回のHTTPレスポンスでページの複数の異なる領域を更新できます。例えば、オンラインショップではこの機能が役立ちます。

routes/partial/cart.tsx
export default function AddToCartPartial() {
  return (
    <>
      <Partial name="cart-items" mode="append">
        {/* 新しいカートアイテムをここにレンダリング */}
      </Partial>
      <Partial name="total-price">
        <p>Total: {totalPrice} €</p>
      </Partial>
    </>
  );
}

この場合、2つのパーシャルが現在のページに適用されます。

パーシャルの置換モード

デフォルトでは、パーシャル内の全コンテンツが置き換えられますが、新しいコンテンツを追加したり、先頭に挿入したりするシナリオもあります。これは、Partialコンポーネントにmodeプロパティを追加することで実現できます。

  • replace - 既存のパーシャルの内容を置き換える(デフォルト)
  • prepend - 新しいコンテンツを既存のコンテンツの前に挿入
  • append - 新しいコンテンツを既存のコンテンツの後に追加

例えば、ログメッセージやリスト形式のデータを表示する場合、appendモードが非常に便利です。

routes/log.tsx
import { Partial } from "$fresh/runtime.ts";

function getNewLogLines() {
    return [
        "hoge", "fuga", "moge"
    ]
}

export default function LogView() {
    const lines = getNewLogLines();
  
    return (
      <Partial name="logs-list" mode="append">
        {lines.map((line) => {
          return <li key={line}>{line}</li>;
        })}
      </Partial>
    );
}

パーシャルの無効化

特定のリンク、フォーム、ボタンなどでパーシャルリクエストを無効にしたい場合、要素または親要素にf-client-nav={false}を設定することでそれを回避できます。

<body f-client-nav>
  {/* This will cause a partial navigation */}
  <a href="/docs/page1">With partials</a>

  {/* This WONT cause a partial navigation */}
  <a href="/docs/page1" f-client-nav={false}>No partials</a>

  {/* This WONT cause a partial navigation on any elements below */}
  <div f-client-nav={false}>
    <div>
      <a href="/docs/page1">No partials</a>
    </div>
  </div>
</body>;

要素がクリックされると、Freshはその要素にf-client-nav属性が設定されているかどうか、またその値がtrueになっているかを確認します。もしその要素自体にf-client-nav属性がなければ、親要素(祖先要素)にf-client-nav属性が設定されているかどうかを確認します。

f-client-nav属性がtrueの要素が見つかった場合、パーシャルリクエストがトリガーされます。もしf-client-nav属性が設定されていないか、falseに設定されている場合は、パーシャルリクエストは発生しません。

この仕組みにより、特定の要素やその親要素でクライアントサイドナビゲーションを制御し、ページの再読み込みを防ぐことができます。

サイトアイコン
公開日
更新日