🐇

[React + Typescript + MUI] アイコン付きテキストナビゲーションの作り方

2024/11/29に公開

概要

本記事は、以前に作成した[React + Typescript + MUI]テキストナビゲーションの作り方の続きです。MUIのアイコンを使ってアイコン+テキストのナビゲーションを作成します。
また、リストのアイテム数が増えるとコードが冗長になるのでリファクタリングを行いコード量を減らします。

前提

  • React + Typescriptの環境が構築済みである
  • react-router-domおよび@mui/materialがインストール済みである
  • Link, NavLink, List, ListItem, ListItemButton, ListItemTextについて理解がある
  • ソースコードは任意のレイアウト用ファイルLayoutTest.tsxに記述しています。

以前の投稿でまとめていますのでそちらもご覧ください
https://zenn.dev/holypotter0304/articles/c9d6de07240138

パッケージのインストール

@mui/icons-material

npm install @mui/icons-material

MUIのアイコンを取得する

MUIにはアイコンが用意されており、インポートをすることで簡単に利用できます。
今回は、HomeとAboutに合うアイコンを取得してインポートします。

  1. Googleで"mui icon"で検索するか、Material Iconsにアクセスする
  2. Search Material Iconsの検索用テキストボックスに"Home"と入力して、使いたいアイコンをクリックする(ここではHomeを選択)
  3. ダイアログボックスが表示されるので、imort文をコピーしてソースコードのに貼り付ける
  4. Aboutも同様に適当な文字列で検索して使用するアイコンのimport文をソースコードに貼り付ける(ここでは"AccountCircle"を選択)

MUIアイコンをアプリで使う

取得したimport文をソースコードに貼り付けて、使いたい場所でコンポーネントを呼び出します。

アイコンをインポートする

LayoutTest.tsx
import HomeIcon from '@mui/icons-material/Home'

リストにアイコンをセットする

アイコンを表示するにはインポートしたコンポーネント名をListItemIconで囲みます

<ListItemIcon>
  <[アイコン名] />
</ListItemIcon>

アイコン付きテキストナビゲーションのソースコード

LayoutTest.tsx
import React from 'react'
import { List, ListItem, ListItemButton, ListItemText } from '@mui/material'
import { NavLink } from 'react-router-dom'
import HomeIcon from '@mui/icons-material/Home'
import AccountCircleIcon from '@mui/icons-material/AccountCircle'

const LayoutTest = () => {
  return (
    <div>
      <List>
        <NavLink to="/">
          <ListItem disablePadding>
            <ListItemButton>
              <ListItemIcon>
                <HomeIcon />
              </ListItemIcon>
              <ListItemText primary="Home" />
            </ListItemButton>
          </ListItem>
        </NavLink>
        <NavLink to="/about">
          <ListItem disablePadding>
            <ListItemButton>
              <ListItemIcon>
                <AccountCircleIcon />
              </ListItemIcon>
              <ListItemText primary="About" />
            </ListItemButton>
          </ListItem>
        </NavLink>
      </List>
    </div>
  )
}
export default LayoutTest

このソースコードの問題点

とりあえず動くものはできましたが、このままではアイテムごとに同じ構成のNavLinkが繰り返されているため読みにくいしコードが冗長なのでリファクタリングをします。

リファクタリング

リファクタリングの方針

  1. アイテムの内容(リンク先, テキスト, アイコンのコンポーネント名)を配列にセットする
  2. map関数を使って配列を個々に取り出してアイテムにセットする

アイテム配列の作成

アイテムの配列は個別のアイテムをセットしたオブジェクトをセットします

オブジェクトのプロパティと型を定義(interface)

Typescriptなのでオブジェクトのプロパティと型を設定します。
このインターフェースはリンク先のURL, 表示テキスト, アイコンのコンポーネントを設定します。
なお、アイコンの型がReact.ComponentTypeなのは、MUIのアイコンはReactのコンポーネントとしてエクスポートされるからです。
少しややこしいですがMUIアイコンを使うときのお作法と考えればいいと思います。

LayoutTest.tsx
interface NavigationItem {
  to: string  // リンク先
  text: string  // 画面に表示するテキスト
  icon: React.ComponentType  // インポートしたMUIアイコン名
}

データをアイテム配列に格納する

実際に設定するデータを配列にセットします。NavigationItem配列に各アイテムのオブジェクトをセットしています。
上で宣言したNavigationItemインターフェースを配列の型にしています。配列ですのでNavigationItem[]となります。

LayoutTest.tsx
const navigationItems: NavigationItem[] = [
  { to: '/', text: 'Home', icon: HomeIcon },
  { to: '/about', text: 'About', icon: AccountCircleIcon },
]

map関数を使って配列を個々に取り出してアイテムにセットする

アイテムオブジェクトを配列に入れた理由はmap関数を使って配列の要素の数だけNavLinkを生成するためです。
※読みやさ重視のためNavLinkclassNameオプションは消しています

LayoutTest.tsx
<List>
  {navigationItems.map((item, index) => (
    <NavLink to={item.to} key={index}>
      <ListItem disablePadding>
        <ListItemButton>
          {<item.icon />}
          <ListItemText primary={item.text} />
        </ListItemButton>
      </ListItem>
    </NavLink>
  ))}
</List>

map関数実装時のポイント

  • map関数の構文は{items.map((item, index) =>(処理))}となり、全体を{}で囲む必要があります。
  • NavLinkkeyオプションはmap関数を使うときはループごとにユニークなkey設定しなければならないという仕様のためにindexをキーにしています。

完成コード

LayoutTest.tsx
import React from 'react'
import { List, ListItem, ListItemButton, ListItemText } from '@mui/material'
import { NavLink } from 'react-router-dom'
import HomeIcon from '@mui/icons-material/Home'
import AccountCircleIcon from '@mui/icons-material/AccountCircle'

interface NavigationItem {
  to: string
  text: string
  icon: React.ComponentType
}

const navigationItems: NavigationItem[] = [
  { to: '/', text: 'Home', icon: HomeIcon },
  { to: '/about', text: 'About', icon: AccountCircleIcon },
]

const LayoutTest = () => {
  return (
    <div>
      <List>
        {navigationItems.map((item, index) => (
          <NavLink
            to={item.to}
            key={index}
            className={({ isActive }) => (isActive ? 'active-link' : 'inactive-link')}
          >
            <ListItem disablePadding>
              <ListItemButton>
                {<item.icon />}
                <ListItemText primary={item.text} />
              </ListItemButton>
            </ListItem>
          </NavLink>
        ))}
      </List>
    </div>
  )
}
export default LayoutTest

留意点や感想など

  • ナビゲーションはHTMLだとすぐに書けますが, Reactにすると難しくかなり勉強することになりました。特にTypescriptは難しいですね。。
  • 次は今回作ったナビゲーションをコンポーネント化し、アイテム配列をpopsで渡すなど再利用を意識したものに変えようと思います

Discussion