もちろんケースバイケースなんだけど、パンくずリストというのは全体のレイアウトに属していながらマウントされる要素に従属して中身が変わるという点でちょっと厄介。
具体的にどういうケースで厄介かというと、ヘッダやサイドバーなどをまとめたpage layout的なコンポーネントを大枠に作り、内部の表示をreact-routerで切り替えている場合。業務アプリとかでやりがちなんじゃないかなとおもう。
ざっくりお気持ち擬似コード。
const PageLayout = (breadcrumbs: readonly Breadcrumb[]) => {
<Layout>
<Header breadcrumbs={breadcrumbs} />
<Sider />
<Main>
{children}
</Main>
<Footer />
</Layout>
};
const Routes = () => {
<PageLayout breadcrumbs={} />
<Switch>
<Router ... />
...
</Switch>
</PageLayout>
};
はい。
まぁ色々とやり方はある。例えばRoutingされるcomponentで、都度PageLayoutを宣言するとか。ReduxのStoreと連動させるとか(ダルい)。
でもダルいよね(ダルいよね)? react-router自体の賛否はともかく、react-routerが意図してる省力化の機能活かせてないし?
さらにさらに、パンくずリストってのは「Routingそのものと密結合」な要素なので、Routingされるcomponent内部でパンくずリストの表示を制御してしまうと「子供のコンポーネントは親に関心を持たない」という鬼原則が崩れてしまう。当たり前だけどPresentationalComponentは描画以外の状態や振る舞いに依存してはいけない。
愚直にやろうとすれば他にも色々あるだろうけど、ルーティングの二重管理とかあまりやりたくない。URL変えるのめんどくさくなるし。
Reactの「親から子へ」の鉄則を守りつつ、DOM上は別の場所で描画してぇ……
はいそうです、Portalがありますね。でもPortalを直接使うとコード上にDOMが露出しちゃって抽象度に欠けますね。ということで思いつきで書いたコードがこれです。
これを使うとこんな風に書けます。
const { Zone, ExitGate, EnterGate } = createSplitGate((arg: BreadcrumbItem[]) => <AwesomeBreadcrumbComponent items={arg} />);
const PageLayout = (breadcrumbs: readonly Breadcrumb[]) => {
<Zone portalID="awesome-id-for-breadcrumb-location">
<Layout>
<Header />
<ExitGate />
<Sider />
<Main>
{children}
</Main>
<Footer />
</Layout>
</Zone>
};
const Routes = () => {
<PageLayout />
<Switch>
<Router ... render={routeProps =>
<>
<EnterGate arg={[un, duex, trois]}
<Presenter ... />
</>
} />
...
</Switch>
</PageLayout>
};
はい。
ExitGate
とか EnterGate
だと汎用的すぎて何が何だかなんで、実運用するときはもっと明示的な名前でaliasを作ってあげましょう。今気がついたけど Enter じゃなくて Entrance のほうが適切か? まぁいいか。
「DOM構造と機能とスタイリングを分離する」ってのはデザインに凝れば凝るほど難しくなるんですが、そういうときにportaは便利なので使っていきましょうねという話でした。
ちなみにほんとは関数名はPortalにしたかったんですが、React本体の名前とだだ被りするのでSplitgateにしました。ゲーマーなら元ネタがわかるとおもうのでSplitgate: Arena Warfare行為しましょう。
以上。