🙌

[React] 初心者講座03 <Componentの役割分担+Routing処理>

2021/11/26に公開

目標

  • 画面遷移するボタンを実装できる
  • viewsディレクトリの階層ごとのComponentを実装できる

共通ヘッダの実装

こんな感じのヘッダ実装を例に説明する。

メニューボタンについては、ハンバーガーメニューやタブが一般的な気もするが、簡易的にボタンをそのまま表示することにした。

ヘッダはどのページでも表示させたいのでenvironmentsにある<Home />が表示を担う。
実装手順のイメージは以下

  • organisms 他アプリケーションでもコピペしたら使えるようなヘッダComponentを実装する
  • ecosystems (他のアプリでの共通化は意識せずに)上記Componentが必要とする値をpropsで与える
  • environments 上記Componentを表示

organisms (AppHeader.tsx)

MUIのAppbarを使用する。
以下の例では、とりあえずReactNodeを受け取ってタイトルの横に並べるために、ReactNodeの配列をpropsに指定している。

ハンバーガーメニューを使用したヘッダにしたければ、ハンバーガーメニューを別のComponentに定義して、ここに表示し、メニューの項目名とクリックした時の処理をpropsで指定する。
そのときのpropsは、
menuItems: {label: string; onClick: () => void}[]
みたいになると思う。

views/organisms/AppHeader.tsx
{/* MUIのAppBarの色を変更 */}
const StyledAppbar = styled(AppBar)({
  backgroundColor: blue[900],
});

export type AppHeaderProps = {
  appTitle: string;
  userName: string;
  leftItems: { id: number; node: React.ReactNode }[];
};

const AppHeader: React.VFC<AppHeaderProps> = (props) => {
  return (
    <StyledAppbar position="static">
      <Toolbar>
        {/* タイトルを表示 */}
        <AppTitleLabel label={props.appTitle} />
	{/* 表示させたいものを並べるだけ */}
        <Box display="flex" gap="8px" ml="8px">
          {props.leftItems.map((item) => (
            <React.Fragment key={item.id}>{item.node}</React.Fragment>
          ))}
        </Box>
        <Box display="flex" alignItems="center" gap="8px" ml="auto">
          <AccountCircleIcon />
          <Box>{props.userName}</Box>
        </Box>
      </Toolbar>
    </StyledAppbar>
  );
};

export default AppHeader;

ecosystems (CRAHeader.tsx)

organismsにタイトルとReactNodeを並べれらるヘッダを作成した。
ecosystemsではそのComponentに値を入れていく。

通信する処理の呼び出しはここ。
(今回、とりあえずユーザ名は固定値で書いた。)

アプリケーション専用のヘッダとするため、タイトルは固定値で与える。
さらに遷移用のボタンも設定する。

ただし、routingを担うのはenvironmentsなのでクリック処理はpropsで受け取る。

views/ecosystems/CRAHeader.tsx
export type CRAHeaderProps = Pick<DashboardButtonProps, 'onDashboardButtonClick'> &
  Pick<DataListButtonProps, 'onDataListButtonClick'>;

const CRAHeader: React.VFC<CRAHeaderProps> = (props) => {
  {/* 本来はuserNameをサーバから取得する */}
  {/* そのうちjson-serverから取ることにする */}
  const userName = 'User Name';

  return (
    <AppHeader
      appTitle="CRA System"
      userName={userName}
      leftItems={[
        { id: 0, node: <DashboardButton onDashboardButtonClick={props.onDashboardButtonClick} /> },
        {
          id: 1,
          node: <DataListButton onDataListButtonClick={props.onDataListButtonClick} />,
        },
      ]}
    />
  );
};

environments (Home.tsx)

routing処理を実装する。
react-router-domuseRouteMatch()<Home />のpathを取得し、それを元にuseHistory()を使って、遷移したい画面のurlをpushすると遷移できる。

遷移処理を作る際に使用しているuseCallbackは簡単に説明すると、何回も同じ関数が生成されるのを防ぐもの。
Reactは再レンダリングが多く、普通のアロー関数だと毎回生成されてしまう。
第2引数の配列の値が変わったときだけ、関数も再生成される。

views/environments/Home.tsx
import { Route, Switch, Redirect, useRouteMatch, useHistory } from 'react-router-dom';
...

const Home: React.VFC = () => {
  const { path } = useRouteMatch();
  const history = useHistory();

  const handleDashboardButtonClick = useCallback((): void => {
    {/* history.push(遷移先のpath) */}
    history.push(`${path}/dashboard`);
  }, [history, path]);
    const handleDataListButtonClick = useCallback((): void => {
    history.push(`${path}/data-list`);
  }, [history, path]);

  return (
    <AppContainer>
      <CRAHeader
        onDashboardButtonClick={handleDashboardButtonClick}
        onDataListButtonClick={handleDataListButtonClick}
      />

      <MainContainer>
        <Switch>
	...

ボタンComponentの実装

さらっと上のコードに書いてあったDashboardButtonとか細かい部品を実装する。
サブディレクトリは自由にしていい。
ボタンは多くなりそうなのでatoms/buttonsを作成しておいた。

atoms (HeaderMenuButton.tsx, DashboardButton.tsx)

まずベースとなるボタンを実装する。
atoms/buttons/basesディレクトリを作った。
見た目の異なるベースボタンはここに実装していくことにする。

ベースとなるボタンを元に様々なボタンを実装していく。
簡易版なのでdisableとかloadingは無しで。

MUIのボタンを使用する。
propsはonClickとボタン名のみ。
こうすることで、アプリケーション内のボタンのデザインを揃えることができる。

views/atoms/buttons/bases/HeaderMenuButton.tsx
import React from 'react';
import Button, { ButtonProps } from '@mui/material/Button';
import { blueGrey } from '@mui/material/colors';

export type HeaderMenuButtonProps = Pick<ButtonProps, 'onClick'> & {
  label: string;
};

const HeaderMenuButton: React.VFC<HeaderMenuButtonProps> = (props) => {
  return (
    <Button
      variant="text"
      onClick={props.onClick}
      sx={{ color: blueGrey[200], fontWeight: 'bold' }}
    >
      {props.label}
    </Button>
  );
};

export default HeaderMenuButton;

先ほど実装したHeaderMenuButtonを使用してDashboardButtonを実装する。
以下が実装例。
propsは別名をつけてるけどPickでonClickのままでもよい。

ボタンレベルでComponent化する理由は、このボタンの名前が変わったときに変更が楽だから。

views/atoms/buttons/DashboardButton.tsx
import React from 'react';
import HeaderMenuButton, { HeaderMenuButtonProps } from './bases/HeaderMenuButton';

export type DashboardButtonProps = {
  onDashboardButtonClick: HeaderMenuButtonProps['onClick'];
};

const DashboardButton: React.VFC<DashboardButtonProps> = (props) => {
  return <HeaderMenuButton onClick={props.onDashboardButtonClick} label="Dashboard" />;
};

export default DashboardButton;

簡易的に実装したが、大体の役割は説明できていると思う。

続き

https://zenn.dev/kosukek/articles/627241ba357c67

https://zenn.dev/kosukek/articles/85604d37ea1b0e

Discussion