1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Hotwire ハンドブック 日本語訳Advent Calendar 2022

Day 3

Turbo 2: Turbo Driveでナビゲーション

Last updated at Posted at 2022-12-02

この記事はGoogle翻訳の結果を編集したものです。

Turbo Driveはページレベルのナビゲーションを強化するTurboの一部です。リンクのクリックとフォームの送信を監視し、それらをバックグラウンドで実行して完全なリロードを行わずにページを更新します。これは、以前はTurbolinksとして知られていたライブラリの進化版です。

ページナビゲーションの基本

Turbo Driveはページナビゲーションをアクションのある場所(URL)への訪問としてモデル化します。

訪問はクリックからレンダリングまでのナビゲーションライフサイクル全体を表します。これにはブラウザ履歴の変更、ネットワークリクエストの発行、キャッシュからのページのコピーの復元、最終的なレスポンスのレンダリング、スクロール位置の更新が含まれます。

訪問には2つのタイプがあります。事前または置換のアクションを持つアプリケーションの訪問と復元のアクションを持つリストアの訪問です。

アプリケーションの訪問

アプリケーションへのアクセスはTurbo Driveが有効なリンクをクリックするか、プログラムでTurbo.visit(location)を呼び出して開始されます。

アプリケーションにアクセスすると常にネットワークリクエストが発行されます。レスポンスが到着するとTurbo DriveはそのHTMLをレンダリングして訪問を完了します。

可能であればTurbo Driveは訪問の開始直後にキャッシュからページのプレビューをレンダリングします。これにより同じページ間を頻繁に移動する速度が向上します。

訪問の場所にアンカーが含まれている場合、Turbo Driveはアンカーされた要素までスクロールしようとします。それ以外の場合はページの上部にスクロールします。

アプリケーションにアクセスするとブラウザーの履歴が変更されます。訪問のアクションによって方法が決まります。

advance.png

デフォルトの訪問アクションはアドバンスです。事前のアクセス中に、Turbo Drivesはhistory.pushStateを使用してブラウザの履歴スタックに新しいエントリーをプッシュします。

Turbo DriveのiOSアダプターを使用するアプリケーションは通常は新しいビューコントローラーをナビゲーションスタックにプッシュすることで事前のアクセスを処理します。同様にAndroidアダプターを使用するアプリケーションは通常は新しいアクティビティをバックスタックにプッシュします。

replace.png

新しい履歴エントリをスタックにプッシュせずに場所を訪問したい場合があります。訪問の置換アクションはhistory.replaceStateを使用して最上位の履歴エントリを破棄し、新しい場所に置き換えます。

リンクをたどると置換訪問がトリガーされるように指定するにはリンクにdata-turbo-action="replace"の注釈を付けます。

<a href="/edit" data-turbo-action="replace">Edit</a>

プログラムで置換アクションを使用して場所にアクセスするにはaction: "replace"オプションをTurbo.visitに渡します。

Turbo.visit("/edit", { action: "replace" })

Turbo DriveのiOSアダプターを使用するアプリケーションは通常は最上位のビューコントローラーを閉じて新しいビューコントローラーをアニメーションなしでナビゲーションスタックにプッシュすることで訪問の置換を処理します。

復元訪問

Turbo Drive は、ブラウザの戻るまたは進むボタンを使用してナビゲートすると、復元訪問を自動的に開始します。iOSまたはAndroidアダプターを使用するアプリケーションは、ナビゲーションスタックを逆方向に移動するときに復元アクセスを開始します。

restore.png

可能であれば、Turbo Driveはリクエストを行わずにキャッシュからページのコピーをレンダリングします。それ以外の場合はネットワーク経由でページの新しいコピーを取得します。詳細についてはキャッシングについてを参照してください。

Turbo Driveは移動する前に各ページのスクロール位置を保存し、復元訪問の時にこの保存された位置に自動的に戻ります。

復元訪問には復元のアクションがあり、Turbo Driveはそれらを内部使用のために予約します。restoreアクションを使ってリンクに注釈を付けたりTurbo.visitを呼び出したりしないでください。

開始前の訪問のキャンセル

アプリケーションへのアクセスはリンクのクリックによって開始されたかTurbo.visitへの呼び出しによって開始されたかに関係なく、開始前にキャンセルできます。

訪問が開始されようとしているときに通知されるturbo:before-visitイベントを監視し、event.detail.url(またはjQuery を使用している場合は$event.originalEvent.detail.url)を使用して訪問の場所を確認します。その後event.preventDefault()を呼び出して訪問をキャンセルします。

復元訪問はキャンセルできず、turbo:before-visitは起動しません。Turbo Driveは通常はブラウザーの戻るまたは進むボタンを介して既に行われた履歴ナビゲーションに応答して復元訪問を発行します。

レンダリングの一時停止

アプリケーションはレンダリングを一時停止し、実行前に追加の準備を行うことができます。

レンダリングが開始されようとしているときに通知されるturbo:before-renderイベントを監視し、event.preventDefault()を使用して一時停止します。準備が完了したら、 event.detail.resume()を呼び出してレンダリングを続行します。

ユースケースの例は訪問の終了アニメーションを追加することです:

document.addEventListener('turbo:before-render', async (event) => {
  event.preventDefault()

  await animateOut()

  event.detail.resume()
})

リクエストの一時停止

アプリケーションはリクエストを一時停止し、実行前に追加の準備を行うことができます。

リクエストが開始されようとしているときに通知されるturbo:before-fetch-requestイベントを監視し、event.preventDefault()を使用して一時停止します。準備が完了したら、 event.detail.resume()を呼び出してリクエストを続けます。

ユースケースの例はリクエストにAuthorizationヘッダーを設定することです:

document.addEventListener('turbo:before-fetch-request', async (event) => {
  event.preventDefault()

  const token = await getSessionToken(window.app)
  event.detail.fetchOptions.headers['Authorization'] = `Bearer ${token}`

  event.detail.resume()
})

別の方法で訪問を行う

デフォルトではリンクをクリックするとサーバーにGETリクエストが送信されます。ただし、data-turbo-methodを使用してこれを次のように変更できます。

<a href="/articles/54" data-turbo-method="delete">Delete the article</a>

リンクはDOMのa要素の横にある非表示のフォームに変換されます。これはフォームをネストできないため、リンクを別のフォーム内に表示できないことを意味します。

また、アクセシビリティ上の理由から、GET以外のものには実際のフォームとボタンを使用することをお勧めします。

特定のリンクまたはフォームでTurbo Driveを無効にする

Turbo Driveは要素またはその祖先にdata-turbo="false"で注釈を付けることにより、要素ごとに無効にすることができます。

<a href="/" data-turbo="false">Disabled</a>

<form action="/messages" method="post" data-turbo="false">
  ...
</form>

<div data-turbo="false">
  <a href="/">Disabled</a>
  <form action="/messages" method="post">
    ...
  </form>
</div>

祖先がオプトアウトしたときに再度有効にするにはdata-turbo="true"を使用します。

<div data-turbo="false">
  <a href="/" data-turbo="true">Enabled</a>
</div>

Turbo Driveが無効になっているリンクまたはフォームはブラウザによって通常どおり処理されます。

Driveをオプトアウトではなくオプトインにしたい場合はTurbo.session.drive = falseを設定してから、要素ごとにDriveを有効にするためにdata-turbo="true"を使用します。TurboをJavaScriptパックにインポートする場合は、これをグローバルに行うことができます。

import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false

進行状況の表示

Turbo Driveのナビゲーション中、ブラウザーにはネイティブの進行状況インジケーターが表示されません。Turbo DriveはCSSベースのプログレスバーをインストールしてリクエストの発行中にフィードバックを提供します。

プログレスバーはデフォルトで有効になっています。読み込みに500ミリ秒以上かかるページでは自動的に表示されます。(この遅延はTurbo.setProgressBarDelayメソッドで変更できます。)

プログレスバーはturbo-progress-barというクラス名を持つ<div>要素です。デフォルトのスタイルはドキュメントの最初に表示され、後で適用されるルールによって上書きできます。

たとえば次のCSSは太い緑色のプログレスバーになります。

.turbo-progress-bar {
  height: 5px;
  background-color: green;
}

プログレス バーを完全に無効にするには、そのvisibilityスタイルをhiddenに設定します。

.turbo-progress-bar {
  visibility: hidden;
}

プログレスバーと連携して、Turbo Driveは訪問またはフォーム送信から開始されたページナビゲーション中にページの<html>要素の[aria-busy]属性も切り替えます。Turbo Driveはナビゲーションの開始時に[aria-busy="true"]を設定して、ナビゲーションの完了時に[aria-busy]属性が削除されます。

アセット変更時のリロード

Turbo Driveはあるページから次のページへの<head>にあるアセット要素のURLを追跡し、それらが変更された場合は自動的に完全なリロードを発行できます。これによりユーザーは常にアプリケーションのスクリプトとスタイルの最新バージョンを使用できます。

アセット要素にdata-turbo-track="reload"の注釈を付け、アセットURLにバージョン識別子を含めます。識別子は次の例のように数値、最終変更のタイムスタンプ、またはアセットのコンテンツのダイジェストにすることができます。

<head>
  ...
  <link rel="stylesheet" href="/application-258e88d.css" data-turbo-track="reload">
  <script src="/application-cbd3cd4.js" data-turbo-track="reload"></script>
</head>

特定のページが完全なリロードをトリガーすることを確認する

ページの<head><meta name="turbo-visit-control">要素を含めることで、特定のページへのアクセスが常に完全なリロードをトリガーするようにすることができます。

<head>
  ...
  <meta name="turbo-visit-control" content="reload">
</head>

この設定はTurbo Driveページの変更とうまくやり取りしないサードパーティのJavaScriptライブラリの回避策として役立つ場合があります。

ルートの場所の設定

デフォルトではTurbo Driveは現在のドキュメントと同じ発信元(つまり、同じプロトコル、ドメイン名、およびポート)を持つURLのみを読み込みます。他のURLへのアクセスはページ全体の読み込みにフォールバックします。

場合によってはTurbo Driveを同じオリジンのパスにさらにスコープしたい場合があります。たとえばTurbo Driveアプリケーションが/appにあり、Turbo Drive以外のヘルプサイトが/helpにある場合、アプリからヘルプサイトへのリンクではTurbo Driveを使用しないでください。

ページの<head><meta name="turbo-root">要素を含めて、Turbo Driveを特定のルートの場所にスコープします。Turbo Driveはこのパスのプレフィックスが付いた同じオリジンのURLのみを読み込みます。

<head>
  ...
  <meta name="turbo-root" content="/app">
</head>

フォーム送信

Turbo Driveはリンクのクリックと同様の方法でフォームの送信を処理します。主な違いはフォーム送信ではHTTP POSTメソッドを使用してステートフルリクエストを発行できるのに対し、リンククリックではステートレスHTTP GETリクエストしか発行できないことです。

送信中、Turbo Driveは<form>要素を対象とする一連のイベントをディスパッチし、ドキュメントをバブルアップします。

  1. turbo:submit-start
  2. turbo:before-fetch-request
  3. turbo:before-fetch-response
  4. turbo:submit-end

送信中、Turbo Driveは送信の開始時に「submitter」要素の無効属性を設定し、送信の終了後に属性を削除します。<form>要素を送信するとき、ブラウザは送信を開始した<input type="submit">または<button>要素をsubmitterとして扱います。<form>要素をプログラムで送信するにはHTMLFormElement.requestSubmit()メソッドを呼び出し、<input type="submit">または<button>要素をオプションのパラメーターとして渡します。

<form>の送信中に他の変更を加えたい場合(たとえば、送信された<form>内のすべてのフィールドを無効にするなど)は独自のイベントリスナーを宣言できます。

addEventListener("turbo:submit-start", ({ target }) => {
  for (const field of target.elements) {
    field.disabled = true
  }
})

フォーム送信後のリダイレクト

フォーム送信からのステートフルリクエストの後、Turbo DriveはサーバーがHTTP 303 リダイレクトレスポンスを返すことを期待します。これをたどってリロードせずにページをナビゲートおよび更新するために使用します。

このルールの例外は応答が4xxまたは5xxステータスコードでレンダリングされる場合です。サーバーが422 Unprocessable Entityで応答することによりフォームバリデーションエラーがレンダリングされ、500 Internal Server Errorで壊れたサーバーが“Something Went Wrong”の画面を表示できます。

TurboがPOSTリクエストからの200での通常のレンダリングを許可しない理由は、ブラウザーがPOSTのリロードに対してTurboが複製できない「このフォームを再度送信しますか?」のダイアログを提示する組み込みの挙動のためです。 代わりに、Turboはフォームアクションに変更するのではなく、レンダリングを試みるフォーム送信時に現在のURLにとどまります。これはリロードがそのアクションのURLに対してGETを発行するためです。

フォーム送信がGETリクエストの場合、フォームのターゲットにdata-turbo-frame指定することで直接レンダリングされたレスポンスを表示できます。レンダリングの一部としてURLを更新する場合はdata-turbo-action属性も渡します。

フォーム送信後のストリーミング

サーバーはレスポンスボディに1つ以上の<turbo-stream>要素があるContent-Type: text/vnd.turbo-stream.htmlヘッダーを送信することにより、Turbo Streamsメッセージでフォーム送信に応答することもできます。これにより移動せずにページの複数の部分を更新できます。

リンクをキャッシュにプリロードする

<a href="/" data-turbo-preload>Home</a>を使用してリンクをTurbo Driveのキャッシュにプリロードします。

これにより最初の訪問前でもページのプレビューが提供されるため、ページの遷移が非常に速く感じられます。これを使用してアプリケーションで最も重要なページをプリロードします。必要のないコンテンツの読み込みにつながるため、過度の使用は避けてください。

また、Eager-Loading FramesまたはLazy-Loading Framesを利用するページにもうまく適合します。興味深いコンテンツが読み込まれている間、ページの構造をプリロードして意味のある読み込み状態をユーザーに示すことができます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?