この記事は React Advent Calendar 2019 の 9日目の記事です。
パンくずリストを ContextAPI で実装したら、使い方の勉強になったので実装方法をまとめました。
Next.js でデモを実装しているので、他の環境で利用する場合は少し置き換えが必要ですが、基本部分は変更せずに再利用可能です。
t-yng/react-breadcrumb-example
目次
パンくずリストコンポーネントの作成
コンポーネントの描画イメージです
reduce()
で渡されたパンくずリストの要素の一覧を セパレーター(>
) で結合をしています。
Link
コンポーネントは Next.js 依存なので、ここだけ自分の環境で適宜置き換えてください。
import React, { FC } from 'react';
import Link from 'next/link';
export interface BreadcrumbItem {
id: number;
text: string;
href?: string;
}
interface BreadcrumbProps {
items: BreadcrumbItem[];
separator?: string;
}
const Breadcrumb: FC<BreadcrumbProps> = ({ items, separator = '>' }) => {
return (
<>
{
items
.map(item =>
item.href != null
// next/link を利用しているので、環境に合わせて書き換えが必要
? <Link href={item.href}>{item.text}</Link>
: <span>{item.text}</span>
)
.reduce((prev, curr) => prev.length === 0 ? [curr] : [...prev, ` ${separator} `, curr], [])
}
</>
)
}
export default Breadcrumb;
Contextの作成
パンくずリストの一覧を設定するための setBreadcrumbItems()
を定義した BreadcrumbContext
を作成します。
ここではインターフェースを定義しているだけで、中身の実装は次の BreadcrumbProvider
で行なっていきます。
import React from 'react';
import { BreadcrumbItem } from './Breadcrumb';
interface BreadcrumbContext {
setBreadcrumbItems: (items: BreadcrumbItem[]) => void;
}
export const BreadcrumbContext = React.createContext({} as BreadcrumbContext);
Providerの作成
上で作成したコンポーネントとContextを組み合わせてパンくずリストを表示する機能を提供するコンポーネントです。
データの流れとしては
- 子コンポーネントが渡された
BreadcrumbContext
のsetBredcrumbItems()
にパンくずリストの一覧を指定して呼び出す - ここで定義されている
setBreadcrumbItems()
が実行されitems
の一覧が更新される -
Breadcrumb
コンポーネントに更新されたitems
が渡されてパンくずリストが描画される
となります。
ページ遷移での描画が残るのを防ぐために、ページ遷移のイベントに合わせて一覧を初期化しています。
next/router
を利用しているので、環境に合わせて書き換えてください。
import React, { useState, FC, HTMLAttributes, useEffect } from 'react';
import { useRouter } from 'next/router';
import { BreadcrumbContext } from './BreadcrumbContext';
import Breadcrumb, { BreadcrumbItem } from './Breadcrumb';
export const BreadcrumbProvider: FC<HTMLAttributes<HTMLElement>> = ({
children,
}) => {
const router = useRouter();
const [items, setItems] = useState([] as BreadcrumbItem[]);
// ページ遷移をした時にパンくずリストを初期化する
useEffect(() => {
// next/router を利用しているので、環境に合わせて書き換えが必要
router.events.on('routeChangeComplete', () => setItems([]));
}, []);
const setBreadcrumbItems = (_items: BreadcrumbItem[]) => {
// コンポーネントの再帰的な描画を防ぐために空の時だけセットする
if (items.length === 0) {
setItems(_items);
}
};
return (
<>
<BreadcrumbContext.Provider value={{ setBreadcrumbItems }}>
{<Breadcrumb items={items} />}
{children}
</BreadcrumbContext.Provider>
</>
);
};
Hooks関数の作成
useContext()
をラップした関数を作成します。
子コンポーネントは BreadcrumbContext
を直接参照せずに、この関数を経由してContextを利用します。
useContext()
を直接呼び出しても問題が無いですが、利用側が中の実装を意識する必要があり気持ち悪いのと、contextの取得後に何か処理を追加したい時に取り回しやすくなるので、面倒がらずにラップしておくことをオススメします。
import { useContext } from 'react';
import { BreadcrumbContext } from './BreadcrumbContext';
import { BreadcrumbItem } from './Breadcrumb';
export const useBreadcrumb = (items: BreadcrumbItem[]) => {
const context = useContext(BreadcrumbContext);
context.setBreadcrumbItems(items);
};
使い方
ルートコンポーネント
export const RootComponent = ({children}) => (
<BreadcrumbProvider>
{children}
</BreadcrumbProvider>
)
子孫コンポーネント
export const ChildComponent = {
useBreadcrumb([
{
id: 1,
text: 'Home',
href: '/'
},
{
id: 2,
text: 'About',
}
]);
return <h1>This is About Page</h1>
}
Comments
Let's comment your feelings that are more than good