読む:TanStack Routerでサクッと始める型安全ルーティング

以下スクラップからの派生
hono(cloudflare pages)の練習としてreact × honoでtodoアプリ作りだしたが、クライアントルーティングが必要になったのでtanstack routerとやらを0から学んでみる。
(react-routerとかもあるけどtanstackをよく聞くので)
これ見る

型安全なのが特徴っぽい。あとキャッシュとかもできるらしい

ふむふむ、とりあえずbun run devでルートコンポーネントを表示するところまでやってみたいな
今回自分はすでにhonoプロジェクト作成済みでそこにtanstackをインストールしたいので、どのようにbun addすれば良いだろうか

これやな。bun add @tanstack/react-router
でいけそう
やってみる

ファイルも合わせて変更してみたけど画面に何も表示されないな...エラーも出てない
ちなみにhono使ってる関係でファイル名をclient.tsxにしないとエラーが出るっぽいんだよな...
設定ファイルもApp.tsxに変えてみたけどエラーになって「client.tsxが見つかりません」みたいになったので、多分Honoは組み込みレベルでclient.tsxを使わないといけないのかも(?)
App.tsxを使えていないのが問題か?なんとかしてApp.tsxという名前にする必要があるのか?

原因分かった。index.tsx内だ
import { Hono } from 'hono'
import { renderToString } from 'react-dom/server'
const app = new Hono()
app.get('*', (c) => {
return c.html(
renderToString(
<html>
<head>
<meta charSet="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
{import.meta.env.PROD ? (
<script type="module" src="/static/client.js"></script>
) : (
<script type="module" src="/src/client.tsx"></script>
)}
</head>
<body>
<div id="root"></div>
</body>
</html>
)
)
})
export default app
ここの
<script type="module" src="/src/client.tsx"></script>
を
<script type="module" src="/src/main.tsx"></script>
に変えたら表示されるようになった

index.tsx内で作ったdomを取得しようとしてるのがmain.tsx内なので、main.tsxと接続?するためにindex.tsx内のscriptタグの中にmain.tsxと書かないといけないみたいな感じか?おそらく

とりあえず、記事内のApp.tsxはclient.tsxに読み替えていくことにする

TanStack Routerには、ルーティングの情報を確認することができる開発者ツールが用意されてるらしい。
インストールする
bun add @tanstack/router-devtools

おー、すごい。いろいろでてきた
(だが邪魔くさいな、別タブで開けないのか?)

へぇ、根本(root)がコンポーネントになることはあんま無いんだ
一般的にはRootにコンポーネントを指定せず、Root以下に別途ルーティングを記述します。

ふむふむ
また、routeTree には、rootRoute に子のルートを追加したものを指定します。addChildren メソッドを利用することで、子のルートを追加することができます。

わお、Next.jsのLinkみたいだ、へぇ
Link コンポーネントを利用することで、リンクを作成することができます。
"use client"
ファイル内で使うimport Link from "next/link"
のやつみたいなことか
ちな'use server'
ファイル内で使いたい場合はimport { redirect } from "next/navigation"
だったな

ん、求めていたのはこれかも?動的にルーティングができるっぽいぞ
また、TanStack Router ではuseNavigateというフックも提供されています。
このフックの返すnavigate関数を利用することで、リダイレクトを行うことができます。
押したらそこに飛ぶ、とかに限らず何かが起きたらそのイベントハンドラ内で強制的に飛ぶ処理を書けるということになりそうなので、やはりこのuseNavigate使えば良さげだな
つまり自分が求めていた「動的に、あるクライアントのページ(リンク先)に飛ばしたい」
というのはリダイレクトと呼べるようだ

ふむふむ、やっぱりそういう使い分けなのか
useNavigateでの遷移については、ユーザが操作した結果起こるものについては利用せず、プログラム的に遷移させる場合に利用することが推奨されています。
ボタンを押下したときに遷移させるような場合は、Linkコンポーネントを利用することが推奨されます。

ほー
また、ネストしたルートのコンポーネント内で
<Outlet>
コンポーネントを利用することで、外枠となるレイアウトを簡単に実装することができます。
<Outlet>
コンポーネントの使い方は、同じルーティングライブラリのreact-router
と似ています。

<Outlet />
が子コンポーネント(children)みたいなイメージっぽい
{children}
と書かずに<Outlet />
と書く、てだけの話か?
ネストされたルートの基本的な使い方としては、親のルートで外枠となるコンポーネントを指定し、children に<Outlet>コンポーネントを指定するのが好ましいでしょう。
その上で、子のルートにコンポーネントを指定することで、外枠のコンポーネントに子のコンポーネントが埋め込まれる形となります。

ふと疑問:
Reactのchildrenとして扱えばいいのに、なぜわざわざOutletというコンポーネントを用意しているんだろう
createRootRouteのcomponentプロパティにはコンポーネントしか指定できない、とか?
jsxではchildren使えるけどtanstackでは扱えないから代わりにOutlet用意した、とか?
どちらにせよchildrenにも対応すればいいのに。できない理由があるのか?
わからん

おーできた、すごい!
これで、/hello/fooにアクセスしたときにHelloコンポーネントが表示されるようになります。
/helloだけでも、/helloまでのコンポーネントがちゃんと表示されるのか、おもしろ
また、/helloにアクセスしたときに、Layoutコンポーネントのヘッダが表示されるのがわかります。なお、中身については空であるため、何も表示されません。

ほー、さっきの開発者ツールでコンポーネントがactiveかどうか(表示されてるかどか)みたいなのが確認できる、すごい。右側のとこ
- /helloだけ
- /hello/foo
表示されてたら(activeだったら)緑になるのか

ほー
TanStack Router では、パス無しのルートを作成することができます。
これは、ルーティングの観点からはあまり意味のない機能です。
しかし、ルーティングの構造を関心事でまとめたい場合には有用であると考えられます。
使い方としては先程のネストされたルートに似ていますが、パスを指定せず、id を指定するところが異なります。

なるほどー、パス無しのルート理解
見た目に影響はまったくないんだけど、開発者が全体像や各部の構造を理解しやすくするために使う感じだな
言うなれば、
「親コンポーネントを作ってその中に子コンポーネント複数つける構造を普通に作りたいが、親コンポーネント自体にまったく内容は必要ないのでただ親として振舞ってくれるだけで良い」
みたいな時にその親にidつけてパス無しのルートにして、子をappendしていく感じで使うと言っても良さそう

$
をつければ、apiエンドポイントでいう:id
みたいに動的にルートを設定できる
んでそのパラメータ取得(apiエンドポイントでいうc.req.params
)は、useParams()
フックで可能

notFoundComponentプロパティにコンポーネントを設定しておけば、それがnotfoundのルートで自動的に表示されるようになる
ルートroute(か親)に設定しておくことが多そう

errorComponentプロパティはエラーの時に表示されるコンポーネント
これはルートrouteとかではなく該当コンポーネントに直接設定

pendingComponentもある。suspenceに対応
試すためにreact-queryをインストール
bun add @tanstack/react-query
useSuspenseQuery()
フックを使用

-
validateSearchプロパティで、クエリパラメータをバリデーションできる
-
なるほど
validateSearch
プロパティには、クエリパラメータをバリデーションする関数を指定します。
この関数は引数として連想配列を受け取ります。キーはstring
、値はunknown
です。
クエリはhoge=fuga
のようにkey=value
の形式で渡されるため、search.hoge
として取得することができます。
-
useSearch()
フックでクエリパラメータ取得できる

応用としてバリデーションライブラリを使用
今回はvalibotというやつ
bun add valibot
おーちゃんとvaidateされてる、すごい(文字列を渡すとエラーになる)

へぇ、ばんばん遅延読み込みしていったほうが良さそうだな
では、どのようなオプションは遅延するべきなのでしょうか?
公式ドキュメントでは、遅延するべきものを以下のように定義しています。
- 通常コンポーネント
- Pending コンポーネント
- エラーコンポーネント
- Not Found コンポーネント
つまり、コンポーネントは遅延読み込みするべきだ、ということです。
詳しくは、以下のリンクを参照してください。

遅延読み込みされてるのが原因だと思うが、やはりちょっと表示が遅いのね
とりあえず読み終わった。めっちゃ助かった、良い記事だ

こちらに戻る