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

:::note
この記事は[Google翻訳](https://translate.google.co.jp/)の結果を編集したものです。
:::

https://turbo.hotwired.dev/handbook/drive

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

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

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

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

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

# アプリケーションの訪問

アプリケーションへのアクセスはTurbo Driveが有効なリンクをクリックするか、プログラムで[Turbo.visit(location)](https://turbo.hotwired.dev/reference/drive#turbodrivevisit)を呼び出して開始されます。

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

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

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

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

![advance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/ed5f5e28-f9db-ca69-83fa-ea5a0aa0d0ce.png)

デフォルトの訪問アクションはアドバンスです。事前のアクセス中に、Turbo Drivesは[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)を使用してブラウザの履歴スタックに新しいエントリーをプッシュします。

Turbo Driveの[iOSアダプター](https://github.com/hotwired/turbo-ios)を使用するアプリケーションは通常は新しいビューコントローラーをナビゲーションスタックにプッシュすることで事前のアクセスを処理します。同様に[Androidアダプター](https://github.com/hotwired/turbo-android)を使用するアプリケーションは通常は新しいアクティビティをバックスタックにプッシュします。

![replace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/93e729b4-b090-67fe-7bf7-92e6717ca077.png)

新しい履歴エントリをスタックにプッシュせずに場所を訪問したい場合があります。訪問の置換アクションは[history.replaceState](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState)を使用して最上位の履歴エントリを破棄し、新しい場所に置き換えます。

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

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

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

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

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

# 復元訪問

Turbo Drive は、ブラウザの`戻る`または`進む`ボタンを使用してナビゲートすると、復元訪問を自動的に開始します。[iOS](https://github.com/hotwired/turbo-ios)または[Android](https://github.com/hotwired/turbo-android)アダプターを使用するアプリケーションは、ナビゲーションスタックを逆方向に移動するときに復元アクセスを開始します。

![restore.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/b9c1d8e5-9f3f-a1af-d3f3-68bfd961480a.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()`を呼び出してレンダリングを続行します。

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

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

  await animateOut()

  event.detail.resume()
})
```

# リクエストの一時停止

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

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

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

```javascript
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`を使用してこれを次のように変更できます。

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

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

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

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

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

```html
<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"`を使用します。

```html
<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パックにインポートする場合は、これをグローバルに行うことができます。

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

# 進行状況の表示

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

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

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

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

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

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

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

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

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

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

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

```html
<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">`要素を含めることで、特定のページへのアクセスが常に完全なリロードをトリガーするようにすることができます。

```html
<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のみを読み込みます。

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

# フォーム送信

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

送信中、Turbo Driveは`<form>`要素を対象とする一連のイベントをディスパッチし、ドキュメントを[バブルアップ](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)します。

1. `turbo:submit-start`
1. `turbo:before-fetch-request`
1. `turbo:before-fetch-response`
1. `turbo:submit-end`

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

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

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

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

フォーム送信からのステートフルリクエストの後、Turbo Driveはサーバーが[HTTP 303 リダイレクトレスポンス](https://en.wikipedia.org/wiki/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を利用するページにもうまく適合します。興味深いコンテンツが読み込まれている間、ページの構造をプリロードして意味のある読み込み状態をユーザーに示すことができます。