LoginSignup
5
2

More than 1 year has passed since last update.

ReactRouterをネストさせて共通パーツの再描画を防ぐ(v6対応)

Last updated at Posted at 2021-01-20

実現したいこと

ReactRouterでページのルーティングを実装しているが、
ルーティングする度にサイトのヘッダー/フッター/サイドナビなど共通パーツまで
各ページで描画していて全然SPAではなかった

共通パーツはルーティングが走っても再描画されないようにしたい

2021/12追記

react-router-dom v6でこの辺の実装方法が変わったので追記
変更点は公式のv5->v6アップグレードガイドだったり
v6版チュートリアル/nested-routesを参考にすると理解しやすいです

環境

React v17.0.1

やったこと

はじめる前

index.js


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>
        <Route exact path="/bbb/page2">
          <Page2 />
        </Route>
        <Route exact path="/bbb/page3">
          <Page3 />
        </Route>
      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

pages.tsx

export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <Header />
     <SideNav />
     <p>Page2</p>
     <Footer />
    </>
  );
}


export function Page3() {
  return (
    <>
     <Header />
     <SideNav />
     <p>Page3</p>
     <Footer />
    </>
  );
}

上記のようなページを3つ持つサイトで以下の特徴を持ちます

  • どのページもHeaderとFooterのコンポーネントを持つ
  • /bbb/* のページ(Page2, Page3)は更に<SideNav>コンポーネントも共通で持つ

これらの共通的な3つのコンポーネントを再描画しないようなRouterを作っていきたい

形だけでもRouterをネストさせる

まず、形の上でもRouterをネストさせてみる
参考にするのは ReactRouterの公式Doc

exampleのコードを見ると<Switch>で分岐させた各Routeのchildren要素の中で更にを使ってRouteの分岐をさせている
真似して書いてみると以下のように

index.js


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>

        {/*今回変更した部分*/}
        <Route exact path="/bbb">
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
        </Route>

      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

まず /bbb でRouteを分岐させることでPage2とPage3をまとめられた
まだ、共通化はしていないので動作としては変わっていない

共通部分をRouterの外に出す

Page2とPage3を比べると差分は<p>要素のみに見える(残りは共通)
そこでRouterを使って描画する部分を<p>要素のみにする

pages.tsx

export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <p>Page2</p>
    </>
  );
}


export function Page3() {
  return (
    <>
     <p>Page3</p>
    </>
  );
}

そして共通化させたい<Header>/<Footer>/<SideNav>コンポーネントは
Page2とPage3を分岐させるRouterの上位に配置する

index.js


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>

        {/*今回変更した部分*/}
        <Route exact path="/bbb">
          <Header />
          <SideNav />
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
          <Footer />
        </Route>

      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

これで /bbb 以下のページ遷移(Page2<>Page3)では共通化した
<Header>/<Footer>/<SideNav>コンポーネントは再描画されなくなった

最後に<Header>/<Footer>/コンポーネントはPage1でも共通なので更に共通化させる

index.js


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Header />
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>
        <Route exact path="/bbb">
          <SideNav />
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
        </Route>
      </Switch>
      <Footer />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

react-routerv6に対応する

上記記法はv5のもので2021/12現在最新のreact router v6では記法が大きく変わっています
今回の書き方に影響するものだけまとめると

  • <Switch>を使わなくても<Route>をネストできるようになった
  • ネストされた<Route>では親<Route>からの相対パスでpathを表現する
  • <Route>に対応するコンポーネントはelement propsで記述する
  • ネスト親のコンポーネント内にネスト子のコンポーネントを表現する<Outlet>コンポーネントが追加

詳細はv6版チュートリアル/nested-routesを見ていただくとしてindex.jsとpaes.tsxがどう変わるかだけ

index.js


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Route path="/" element={<PageFrame />}>
        <Route path="aaa/page1" element={<Page1 />} />
        <Route path="bbb/" element={<WithSideNav />}>
          <Route path="page2" element={<Page2 />} />
          <Route path="page3" element={<Page3 />} />
        </Route>
      </Route>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

pages.tsx

export function PageFrame() {
  return (
    <>
     <Header />
     <Outlet />
     <Footer />
    </>
  );
}

export function WithSideNav() {
  return (
    <>
     <SadeNav />
     <Outlet />
    </>
  );
}

export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <p>Page2</p>
    </>
  );
}


export function Page3() {
  return (
    <>
     <p>Page3</p>
    </>
  );
}

Routerのネストに必要な要素が少なくなり
記述コスト、視認性が圧倒的に良くなっている。 これが公式サポートの力...
各コンポーネント側もネスト子要素をchileden -> Outletと特別扱いすることで
より取り回しがしやすくなったのではないかと

完成

当初やりたかったことは実装できた
ほとんど公式チュートリアルにあった通りだが、実際に使ってみた方が理解しやすいなぁ

個人的にはRouterのchildren要素はスッキリさせたいので/bbb以下のchildren要素をコンポーネント化するかと思う

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2