ページA、ページBに共通するコンポーネント(往復する■)を読み込んだ際、普通に実装するとページを遷移するたびに■の位置は初期値に戻るので、タイミングによってはワープしたように見えます。
これをなんとかできないものかと調べてみたところ、Layoutsを使えば実現できることがわかりました。
ざっくりいえば、
- app.tsx(app.jsx)に設置したコンポーネントはページを跨いでもunmountされない
- pageにgetLayoutという関数を生やしてappで呼べばページによって異なるコンポーネントをapp.tsx(app.jsx)に渡せる
ということでした。
実践
pages/a.tsx
import Link from 'next/link'; import Canvas from '../components/Canvas'; export default function A() { return ( <> <Canvas /> <ul> <li> <Link href="/a"> <a>A</a> </Link> </li> <li> <Link href="/b"> <a>B</a> </Link> </li> </ul> <p>A</p> </> ); }
pages/b.tsx
import Link from 'next/link'; import Canvas from '../components/Canvas'; export default function B() { return ( <> <Canvas /> <ul> <li> <Link href="/a"> <a>A</a> </Link> </li> <li> <Link href="/b"> <a>B</a> </Link> </li> </ul> <p>B</p> </> ); }
まずは普通に実装します。
当然、ページ遷移ともに、Canvasは初期化されます。
では、共通パーツをapp.tsxに移動してみましょう。
pages/app.tsx
import { AppProps } from 'next/app'; import Link from 'next/link'; import Canvas from '../components/Canvas'; function MyApp({ Component, pageProps }: AppProps) { return( <> <Canvas /> <ul> <li> <Link href="/a"> <a>A</a> </Link> </li> <li> <Link href="/b"> <a>B</a> </Link> </li> </ul> <Component {...pageProps} /> </> ); } export default MyApp;
pages/a.tsx
export default function A() { return ( <p>A</p> ); }
pages/b.tsx
export default function B() { return ( <p>B</p> ); }
ページを遷移してもCanvasが初期化されなくなりました。
見栄えを良くするために、共通部をコンポーネント化します。
components/Layout.tsx
import Link from 'next/link'; import { ReactNode } from 'react'; import Canvas from './Canvas'; export default function Layout({ children }: { children: ReactNode; }) { return ( <> <Canvas /> <ul> <li> <Link href="/a"> <a>A</a> </Link> </li> <li> <Link href="/b"> <a>B</a> </Link> </li> </ul> { children } </> ); }
pages/app.tsx
import { AppProps } from 'next/app'; import Layout from '../components/Layout'; function MyApp({ Component, pageProps }: AppProps) { return( <Layout> <Component {...pageProps} /> </Layout> ); } export default MyApp;
page/a.tsx
export default function A() { return ( <p>A</p> ); }
page/b.tsx
export default function B() { return ( <p>B</p> ); }
見栄えが良くなりました。
そして、ページ遷移してもCanvasは初期化されません。
これで良いといえば良いのですが、今後Canvasを表示しないページCが出てくることもあるかもしれないので、Layoutをapp.tsxで読み込むのではなく、ページ側で読み込むようにします。
pages/app.tsx
import { NextPage } from 'next'; import { AppProps } from 'next/app'; import { ReactElement, ReactNode } from 'react'; type NextPageWithLayout = NextPage & { getLayout?: (page: ReactElement) => ReactNode; }; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; function MyApp({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page) // ComponentにgetLayoutが定義されてない場合は引数をそのままreturnする関数を使う return getLayout( <Component {...pageProps} /> ); } export default MyApp;
pages/a.tsx
import { ReactNode } from 'react'; import Layout from '../components/Layout'; export default function A() { return ( <p>A</p> ); } A.getLayout = function getLayout(page: ReactNode) { return ( <Layout>{ page }</Layout> ); }
pages/b.tsx
import { ReactNode } from 'react'; import Layout from '../components/Layout'; export default function B() { return ( <p>B</p> ); } B.getLayout = function getLayout(page: ReactNode) { return ( <Layout>{ page }</Layout> ); }
挙動も問題ありません。
これで、将来的にCanvasが必要ないページが出てきても安心です。