データローディング
Remix の主要な機能の 1 つは、サーバーとのやり取りを簡素化して、コンポーネントにデータを取得することです。これらの規約に従うと、Remix は自動的に次のことを行えます。
- ページをサーバーレンダリングする
- JavaScript のロードに失敗した場合のネットワーク状態に対する耐性
- ユーザーがサイトを操作する際に、ページの変更部分のデータのみをロードすることで高速化する最適化を行う
- 遷移時にデータ、JavaScript モジュール、CSS、その他のアセットを並行してフェッチし、UI が途切れ途切れになる原因となるレンダリング + フェッチのウォーターフォールを回避する
- アクション後に再検証することで、UI のデータがサーバー上のデータと同期していることを確認する
- (ドメインをまたいでも) 前後クリック時の優れたスクロール復元
- エラー境界によるサーバー側のエラーの処理
- エラー境界による「Not Found」および「Unauthorized」に対する堅牢な UX の実現
- UI のハッピーパスを維持するのに役立つ
基本
各ルートモジュールは、コンポーネントと loader
をエクスポートできます。useLoaderData
は、ローダーのデータをコンポーネントに提供します。
コンポーネントはサーバーとブラウザーでレンダリングされます。ローダーは サーバーでのみ実行されます。つまり、ハードコードされた製品配列はブラウザーバンドルに含まれず、データベース、決済処理、コンテンツ管理システムなどの API や SDK にサーバーのみを使用しても安全です。
サーバー側のモジュールがクライアントバンドルに含まれてしまう場合は、サーバーとクライアントのコード実行に関するガイドを参照してください。
ルートパラメーター
app/routes/users.$userId.tsx
や app/routes/users.$userId.projects.$projectId.tsx
のように、ファイルに $
を付けて名前を付けると、動的なセグメント ($
で始まるもの) が URL から解析され、params
オブジェクトでローダーに渡されます。
次の URL が与えられた場合、パラメーターは次のように解析されます。
URL | params.userId | params.projectId |
---|
/users/123/projects/abc | "123" | "abc" |
/users/aec34g/projects/22cba9 | "aec34g" | "22cba9" |
これらのパラメーターは、データの検索に最も役立ちます。
パラメーターの型安全性
これらのパラメーターはソースコードではなく URL から取得されるため、定義されているかどうかを確実に知ることはできません。そのため、パラメーターのキーの型は string | undefined
になっています。特に TypeScript で型安全性を確保するには、使用する前に検証することをお勧めします。invariant
を使用すると簡単になります。
失敗した場合に invariant
でこのようなエラーをスローすることに抵抗があるかもしれませんが、Remix では、ユーザーは壊れた UI ではなく、問題から回復できるエラー境界に到達することを覚えておいてください。
外部 API
Remix はサーバー上で fetch
API をポリフィルするため、既存の JSON API からデータをフェッチするのが非常に簡単です。状態、エラー、競合状態などを自分で管理する代わりに、ローダー (サーバー上) からフェッチを実行し、残りは Remix に任せることができます。
これは、すでに使用する API があり、Remix アプリでデータソースに直接接続することを気にしない場合や必要がない場合に最適です。
データベース
Remix はサーバー上で実行されるため、ルートモジュールでデータベースに直接接続できます。たとえば、Prisma を使用して Postgres データベースに接続できます。
そして、ルートはそれをインポートしてクエリを実行できます。
TypeScript を使用している場合は、useLoaderData
を呼び出すときに型推論を使用して Prisma Client で生成された型を使用できます。これにより、ロードされたデータを使用するコードを記述する際に、型安全性とインテリセンスが向上します。
Cloudflare KV
環境として Cloudflare Pages または Workers を選択した場合、Cloudflare Key Value ストレージを使用すると、静的リソースであるかのようにエッジにデータを永続化できます。
Pages の場合、ローカル開発を開始するには、package.json タスクに --kv
パラメーターと名前空間の名前を追加する必要があります。次のようになります。
"dev:wrangler": "cross-env NODE_ENV=development wrangler pages dev ./public --kv PRODUCTS_KV"
Cloudflare Workers 環境の場合は、他の構成を行う必要があります。
これにより、ローダーコンテキストで PRODUCTS_KV
を使用できるようになります (KV ストアは Cloudflare Pages アダプターによってローダーコンテキストに自動的に追加されます)。
Not Found
データをロードしているときに、レコードが「見つからない」ことはよくあります。コンポーネントを期待どおりにレンダリングできないことがわかったらすぐに、レスポンスを throw
すると、Remix は現在のローダーでのコードの実行を停止し、最も近いエラー境界に切り替えます。
URL 検索パラメーター
URL 検索パラメーターは、?
の後の URL の部分です。これの他の名前は、「クエリ文字列」、「検索文字列」、または「ロケーション検索」です。request.url
から URL を作成することで値にアクセスできます。
ここでは、いくつかの Web プラットフォームの型が使用されています。
次の URL が与えられた場合、検索パラメーターは次のように解析されます。
URL | url.searchParams.get("term") |
---|
/products?term=stretchy+pants | "stretchy pants" |
/products?term= | "" |
/products | null |
データのリロード
複数のネストされたルートがレンダリングされていて、検索パラメーターが変更された場合、(新しいルートまたは変更されたルートだけでなく) すべてのルートがリロードされます。これは、検索パラメーターが横断的な関心事であり、どのローダーにも影響を与える可能性があるためです。このシナリオで一部のルートがリロードされないようにする場合は、shouldRevalidateを使用してください。
コンポーネント内の検索パラメーター
ローダーやアクションではなく、コンポーネントから検索パラメーターを読み取って変更する必要がある場合があります。ユースケースに応じて、これを行う方法はいくつかあります。
検索パラメーターの設定
検索パラメーターを設定する最も一般的な方法は、ユーザーがフォームでそれらを制御できるようにすることです。
ユーザーが 1 つだけ選択した場合:
すると、URL は /products/shoes?brand=nike
になります。
ユーザーが両方を選択した場合:
すると、URL は /products/shoes?brand=nike&brand=adidas
になります。
両方のチェックボックスに "brand"
という名前が付けられているため、brand
が URL 検索文字列で繰り返されていることに注意してください。ローダーでは、searchParams.getAll
を使用して、これらのすべての値にアクセスできます。
検索パラメーターへのリンク
開発者として、検索文字列を含む URL にリンクすることで、検索パラメーターを制御できます。リンクは、URL の現在の検索文字列 (存在する場合) をリンク内の文字列に置き換えます。
コンポーネントでの検索パラメーターの読み取り
ローダーで検索パラメーターを読み取るだけでなく、コンポーネントでもアクセスする必要があることがよくあります。
フィールドの変更時にフォームを自動送信したい場合は、useSubmit
があります。
命令的な検索パラメーターの設定
一般的ではありませんが、いつでも理由を問わず、命令的に searchParams を設定することもできます。ここでのユースケースはわずかであり、良い例を思い付くことさえできませんでしたが、ここに簡単な例を示します。
検索パラメーターと制御された入力
多くの場合、チェックボックスなどの一部の入力を URL の検索パラメーターと同期させたいと考えています。これは、React の制御されたコンポーネントの概念では少しトリッキーになる可能性があります。
これは、検索パラメーターを 2 つの方法で設定でき、入力を検索パラメーターと同期させたい場合にのみ必要です。たとえば、このコンポーネントでは、<input type="checkbox">
と Link
の両方でブランドを変更できます。
ユーザーがチェックボックスをクリックしてフォームを送信すると、URL が更新され、チェックボックスの状態も変更されます。ただし、ユーザーがリンクをクリックすると、URL のみが更新され、チェックボックスは更新されません。これは私たちが望むものではありません。ここでは、React の制御されたコンポーネントに精通しており、defaultChecked
の代わりに checked
に切り替えることを考えるかもしれません。
これで、反対の問題が発生しました。リンクをクリックすると、URL とチェックボックスの状態の両方が更新されますが、チェックボックスは機能しなくなりました。これは、それを制御する URL が変更されるまで React が状態の変更を防止するためです。チェックボックスを変更してフォームを再送信することはできないため、URL は変更されません。
React は、何らかの状態を使用して制御することを望んでいますが、フォームを送信するまでユーザーに制御させ、変更されたら URL に制御させたいと考えています。そのため、この「半制御」状態になっています。
2 つの選択肢があり、どちらを選択するかは、必要なユーザーエクスペリエンスによって異なります。
最初の選択肢: 最も簡単なことは、ユーザーがチェックボックスをクリックしたときにフォームを自動送信することです。
(フォームの onChange
でも自動送信する場合は、イベントがフォームにバブリングしないように e.stopPropagation()
を必ず実行してください。そうしないと、チェックボックスをクリックするたびに二重送信が発生します。)
2 番目の選択肢: チェックボックスが URL の状態を反映し、ユーザーがフォームを送信して URL を変更する前にオンとオフを切り替えることもできる「半制御」入力を希望する場合は、いくつかの状態を配線する必要があります。少し手間がかかりますが、簡単です。
- 検索パラメーターからいくつかの状態を初期化します
- ユーザーがチェックボックスをクリックしたときに状態を更新して、ボックスが「チェック済み」に変更されるようにします
- 検索パラメーターが変更されたとき (ユーザーがフォームを送信したか、リンクをクリックしたとき) に状態を更新して、URL 検索パラメーターの内容を反映します
このようなチェックボックスの抽象化を作成することもできます。
オプション 3: 選択肢は 2 つしかないと言いましたが、React をよく知っている場合は、3 番目の不敬な選択肢に誘惑される可能性があります。key
プロップの仕掛けを使用して入力を消去して再マウントしたいと思うかもしれません。賢い方法ですが、ユーザーがクリックした後に React がドキュメントからノードを削除すると、ユーザーがフォーカスを失うため、アクセシビリティの問題が発生します。
これはしないでください。アクセシビリティの問題が発生します
Remix の最適化
Remix は、ナビゲーション時に変更されるページのパーツのデータのみをロードすることで、ユーザーエクスペリエンスを最適化します。たとえば、このドキュメントで使用している UI を考えてみましょう。サイドのナビゲーションバーは、すべてのドキュメントの動的に生成されたメニューをフェッチした親ルートにあり、子ルートは現在読んでいるドキュメントをフェッチしました。サイドバーのリンクをクリックすると、Remix は親ルートがページに残ることを認識しますが、ドキュメントの URL パラメーターが変更されるため、子ルートのデータは変更されます。この洞察により、Remix は 親ルートのデータを再フェッチしません。
Remix がない場合、次の質問は「すべてのデータをリロードするにはどうすればよいか」です。これは Remix にも組み込まれています。アクションが呼び出されると (ユーザーがフォームを送信したか、プログラマーが useSubmit
から submit
を呼び出した場合)、Remix はページ上のすべてのルートを自動的にリロードして、発生した可能性のある変更をキャプチャします。
ユーザーがアプリを操作するときに、キャッシュの期限切れやデータの過剰フェッチを心配する必要はありません。すべて自動です。
Remix がすべてのルートをリロードするケースは 3 つあります。
- アクション後 (フォーム、
useSubmit
、fetcher.submit
)
- URL 検索パラメーターが変更された場合 (どのローダーでも使用できる可能性があります)
- ユーザーがすでにいるまったく同じ URL へのリンクをクリックした場合 (これにより、履歴スタックの現在のエントリも置き換えられます)
これらの動作はすべて、ブラウザーのデフォルトの動作をエミュレートします。これらの場合、Remix はコードを最適化するのに十分な情報を把握していませんが、shouldRevalidate を使用して自分で最適化できます。
データライブラリ
Remix のデータ規約とネストされたルートのおかげで、通常は React Query、SWR、Apollo、Relay、urql
などのクライアント側のデータライブラリに手を伸ばす必要がないことがわかります。主にサーバー上のデータとやり取りするために redux などのグローバル状態管理ライブラリを使用している場合も、それらが必要になる可能性は低いでしょう。
もちろん、Remix はそれらの使用を妨げるものではありません (バンドラーの統合が必要な場合を除く)。好きな React データライブラリを持ち込んで、Remix API よりも UI に適していると思われる場所で使用できます。場合によっては、最初のサーバーレンダリングに Remix を使用し、その後、インタラクションのために好きなライブラリに切り替えることができます。
ただし、外部データライブラリを持ち込み、Remix 独自のデータ規約を回避すると、Remix は自動的に次のことを行えなくなります。
- ページをサーバーレンダリングする
- JavaScript のロードに失敗した場合のネットワーク状態に対する耐性
- ユーザーがサイトを操作する際に、ページの変更部分のデータのみをロードすることで高速化する最適化を行う
- 遷移時にデータ、JavaScript モジュール、CSS、その他のアセットを並行してフェッチし、UI が途切れ途切れになる原因となるレンダリング + フェッチのウォーターフォールを回避する
- アクション後に再検証することで、UI のデータがサーバー上のデータと同期していることを確認する
- (ドメインをまたいでも) 前後クリック時の優れたスクロール復元
- エラー境界によるサーバー側のエラーの処理
- エラー境界による「Not Found」および「Unauthorized」に対する堅牢な UX の実現
- UI のハッピーパスを維持するのに役立つ
代わりに、優れたユーザーエクスペリエンスを提供するために追加の作業を行う必要があります。
Remix は、設計できるあらゆるユーザーエクスペリエンスに対応できるように設計されています。外部データライブラリが 必要 であることは予想外ですが、それでも 必要 である可能性があり、それはそれで問題ありません。
Remix を学習するにつれて、クライアントの状態での思考から URL での思考に移行し、そうすることで多くのものを無料で手に入れることができるでしょう。
注意点
ローダーは、ブラウザーからの fetch
を介してサーバーでのみ呼び出されるため、データは JSON.stringify
でシリアル化され、コンポーネントに到達する前にネットワーク経由で送信されます。つまり、データはシリアル化可能である必要があります。例:
これは機能しません!
すべてがうまくいくわけではありません! ローダーは データ 用であり、データはシリアル化可能である必要があります。
一部のデータベース (FaunaDB など) は、ローダーから返す前にシリアル化に注意する必要があるメソッドを持つオブジェクトを返します。通常、これは問題ではありませんが、データがネットワーク経由で転送されることを理解しておくことをお勧めします。
さらに、Remix はローダーを自動的に呼び出します。ローダーを直接呼び出そうとしないでください。
これは機能しません