Closed31

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

Yug (やぐ)Yug (やぐ)

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

Yug (やぐ)Yug (やぐ)

ふむふむ、とりあえずbun run devでルートコンポーネントを表示するところまでやってみたいな

今回自分はすでにhonoプロジェクト作成済みでそこにtanstackをインストールしたいので、どのようにbun addすれば良いだろうか

Yug (やぐ)Yug (やぐ)

ファイルも合わせて変更してみたけど画面に何も表示されないな...エラーも出てない

ちなみにhono使ってる関係でファイル名をclient.tsxにしないとエラーが出るっぽいんだよな...
設定ファイルもApp.tsxに変えてみたけどエラーになって「client.tsxが見つかりません」みたいになったので、多分Honoは組み込みレベルでclient.tsxを使わないといけないのかも(?)

App.tsxを使えていないのが問題か?なんとかしてApp.tsxという名前にする必要があるのか?

Yug (やぐ)Yug (やぐ)

原因分かった。index.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>
に変えたら表示されるようになった

Yug (やぐ)Yug (やぐ)

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

Yug (やぐ)Yug (やぐ)

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

Yug (やぐ)Yug (やぐ)

TanStack Routerには、ルーティングの情報を確認することができる開発者ツールが用意されてるらしい。

インストールする

bun add @tanstack/router-devtools
Yug (やぐ)Yug (やぐ)

おー、すごい。いろいろでてきた

(だが邪魔くさいな、別タブで開けないのか?)

Yug (やぐ)Yug (やぐ)

へぇ、根本(root)がコンポーネントになることはあんま無いんだ

一般的にはRootにコンポーネントを指定せず、Root以下に別途ルーティングを記述します。

Yug (やぐ)Yug (やぐ)

ふむふむ

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

Yug (やぐ)Yug (やぐ)

わお、Next.jsのLinkみたいだ、へぇ

Link コンポーネントを利用することで、リンクを作成することができます。

"use client"ファイル内で使うimport Link from "next/link"のやつみたいなことか

ちな'use server'ファイル内で使いたい場合はimport { redirect } from "next/navigation"だったな

Yug (やぐ)Yug (やぐ)

ん、求めていたのはこれかも?動的にルーティングができるっぽいぞ

また、TanStack Router ではuseNavigateというフックも提供されています。
このフックの返すnavigate関数を利用することで、リダイレクトを行うことができます。

押したらそこに飛ぶ、とかに限らず何かが起きたらそのイベントハンドラ内で強制的に飛ぶ処理を書けるということになりそうなので、やはりこのuseNavigate使えば良さげだな

つまり自分が求めていた「動的に、あるクライアントのページ(リンク先)に飛ばしたい」
というのはリダイレクトと呼べるようだ

Yug (やぐ)Yug (やぐ)

ふむふむ、やっぱりそういう使い分けなのか

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

Yug (やぐ)Yug (やぐ)

ほー

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

Yug (やぐ)Yug (やぐ)

<Outlet />が子コンポーネント(children)みたいなイメージっぽい

{children}と書かずに<Outlet />と書く、てだけの話か?

ネストされたルートの基本的な使い方としては、親のルートで外枠となるコンポーネントを指定し、children に<Outlet>コンポーネントを指定するのが好ましいでしょう。
その上で、子のルートにコンポーネントを指定することで、外枠のコンポーネントに子のコンポーネントが埋め込まれる形となります。

Yug (やぐ)Yug (やぐ)

ふと疑問:
Reactのchildrenとして扱えばいいのに、なぜわざわざOutletというコンポーネントを用意しているんだろう

createRootRouteのcomponentプロパティにはコンポーネントしか指定できない、とか?
jsxではchildren使えるけどtanstackでは扱えないから代わりにOutlet用意した、とか?

どちらにせよchildrenにも対応すればいいのに。できない理由があるのか?

わからん

Yug (やぐ)Yug (やぐ)

おーできた、すごい!

これで、/hello/fooにアクセスしたときにHelloコンポーネントが表示されるようになります。

/helloだけでも、/helloまでのコンポーネントがちゃんと表示されるのか、おもしろ

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

Yug (やぐ)Yug (やぐ)

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

  • /helloだけ

  • /hello/foo

表示されてたら(activeだったら)緑になるのか

Yug (やぐ)Yug (やぐ)

ほー

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

Yug (やぐ)Yug (やぐ)

なるほどー、パス無しのルート理解

見た目に影響はまったくないんだけど、開発者が全体像や各部の構造を理解しやすくするために使う感じだな

言うなれば、

「親コンポーネントを作ってその中に子コンポーネント複数つける構造を普通に作りたいが、親コンポーネント自体にまったく内容は必要ないのでただ親として振舞ってくれるだけで良い」

みたいな時にその親にidつけてパス無しのルートにして、子をappendしていく感じで使うと言っても良さそう

Yug (やぐ)Yug (やぐ)

$をつければ、apiエンドポイントでいう:idみたいに動的にルートを設定できる

んでそのパラメータ取得(apiエンドポイントでいうc.req.params)は、useParams()フックで可能

Yug (やぐ)Yug (やぐ)

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

ルートroute(か親)に設定しておくことが多そう

Yug (やぐ)Yug (やぐ)

errorComponentプロパティはエラーの時に表示されるコンポーネント

これはルートrouteとかではなく該当コンポーネントに直接設定

Yug (やぐ)Yug (やぐ)

pendingComponentもある。suspenceに対応

試すためにreact-queryをインストール

bun add @tanstack/react-query

useSuspenseQuery()フックを使用

Yug (やぐ)Yug (やぐ)
  • validateSearchプロパティで、クエリパラメータをバリデーションできる

  • なるほど

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

  • useSearch()フックでクエリパラメータ取得できる
Yug (やぐ)Yug (やぐ)

応用としてバリデーションライブラリを使用
今回はvalibotというやつ

bun add valibot

おーちゃんとvaidateされてる、すごい(文字列を渡すとエラーになる)

Yug (やぐ)Yug (やぐ)

へぇ、ばんばん遅延読み込みしていったほうが良さそうだな

では、どのようなオプションは遅延するべきなのでしょうか?
公式ドキュメントでは、遅延するべきものを以下のように定義しています。

  • 通常コンポーネント
  • Pending コンポーネント
  • エラーコンポーネント
  • Not Found コンポーネント

つまり、コンポーネントは遅延読み込みするべきだ、ということです。
詳しくは、以下のリンクを参照してください。

Yug (やぐ)Yug (やぐ)

遅延読み込みされてるのが原因だと思うが、やはりちょっと表示が遅いのね

とりあえず読み終わった。めっちゃ助かった、良い記事だ

このスクラップは2024/12/30にクローズされました