Upgrade to Pro — share decks privately, control downloads, hide ads and more …

責務の分離が生みだす葛藤

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →
Avatar for woody woody
September 10, 2026

 責務の分離が生みだす葛藤

Avatar for woody

woody

September 10, 2026

Other Decks in Programming

Transcript

  1. 出発点:画面とルーティングを分けた設計 ルーティングライブラリにTanstack Router を使用 遷移先はルートツリー内で管理 TanStack Router Reactアプリ向けのルーティングライブラリ ルートやパラメーターの型を自動で推論してくれる ファイルベースでルート階層を定義できる

    ルーティング 画面 1 2 3 4 5 6 7 8 9 10 src/ ├── (ルーティング)/ │ ├── __root.tsx │ ├── index.tsx │ └── posts/ │ ├── index.tsx │ └── $postId.tsx └── (画面)/ ├── PostListScreen.tsx └── PostDetailScreen.tsx
  2. 出発点:画面とルーティングを分けた設計 navigate() useNavigate()から取得、 命令的に関数を呼び出して遷移(処理)を実行する 画面 1 // 画面: 遷移先は知らない。操作の意図をコールバックとして渡すだけ。 2

    function Screen({ onNavigateToDetail }) { 3 return ( 4 <h1>画面見出し</h1> 5 ... 6 <button onClick={onNavigateToDetail}>詳細へ</button> 7 ); 8 } ルーティング 1 // ルーティング側: 遷移先の決定をここに閉じ込める 2 function Route() { 3 const navigate = useNavigate(); 4 return <Screen onNavigateToDetail={ 5 () => navigate({ to: "/detail" }) 6 } />; 7 } ビジネスロジックもルーティングも責務を分離できた!画面コンポーネントはシンプルだ!🌟
  3. 問題:分離に集中して見落としたもの ボタン...?🤔 画面 1 // 画面: 遷移先は知らない。操作の意図をコールバックとして渡すだけ。 2 function Screen({

    onNavigateToDetail }) { 3 return ( 4 <h1>画面見出し</h1> 5 ... 6 <button onClick={onNavigateToDetail}>詳細へ</button> 7 ); 8 } → 画面遷移はアンカー(<a>タグ)で実装するのが適切 ホバー時のプリフェッチ 状態の判定 リンクに対するブラウザ標準機能 SEO, アクセシビリティ
  4. 悩む:テスト容易性と実装コストで試行錯誤 <a>タグに修正する Link 画面 <a> タグでのリンク遷移 + 型安全にパラメーターも渡せる 1 function

    Screen({ onNavigateToDetail }) { 2 return ( 3 <h1>画面見出し</h1> 4 ... 5 <button onClick={onNavigateToDetail}>詳細へ</button> 6 ); 7 } 遷移先の情報を画面側でもつ必要がある テスト側で別途Router コンテキストの用意が必要 1 import { Link } from "@tanstack/react-router"; 2 3 function Screen() { 4 return ( 5 <h1>画面見出し</h1> 6 ... 7 <Link to="/detail">詳細へ</Link> 8 ); 9 }
  5. 試行錯誤 コンポーネント注入 createLink():<Link> と同じ型安全なナビゲーション機能を任意のコンポーネントに付与 画面 1 // 画面側: 遷移先を知らない。渡された props(href・onClick

    等)をそのまま <a> へ転送するだけ。 2 function DetailLink(props) { 3 return <a {...props} />; 4 } 5 6 function Screen({ detailLink: DetailLink }) { 7 return <DetailLink>詳細へ</DetailLink>; 8 } ルーティング 1 // ルーティング層: 遷移先(to)を注入した「本物のリンク」を作る 2 const DetailRouterLink = createLink(DetailLink); 3 4 function Route() { 5 return ( 6 <Screen detailLink={(props) => <DetailRouterLink to="/ detail" {...props} />} /> 7 ); 8 } 遷移先をルーティング層へ分離することはできたけど、、、