KaQiita

新米社会人が適当なことを書いてます。温かく見守ってやってください。

【Rails & React】再利用性を意識してコンポーネントを切る

はじめに

以前、このような記事を書きました。

www.kaqiita.com

view を書くときにはデザインの見た目通りではなく、それぞれのコンポーネントの役割を抽象化して書いた方が綺麗に書けるよ、というものです。

こんな記事を書いていたにも関わらず、先日先輩エンジニアからのコードレビューで同じことを指摘されてしまったので、再度まとめようと思います。

今回は React で例を書いてみようと思います。

今回の例

以下のようなアコーディオンデザインの検索画面を実装しようとしていました。

f:id:KakkiiiiKyg:20190401144928p:plain

つまり路線名がアコーディオンデザインになっていて、クリックするとその路線の駅名が出てくるというものです。

このモックをコーディングするために、以下のようなコンポーネントを考えました。

railwayLineAccordion.jsx

import React from 'react';
import PropTypes from 'prop-types';

class railwayLineAccordion extends React.Component {
   constructor(props) {
    super(props);
    this.state = { isOpen: props.initialOpenState };
  }

   handleClick = () => {
    this.setState(prevState => ({ isOpen: !prevState.isOpen }));
  };

   render() {
    const { railwayLine } = this.props;
    const { isOpen } = this.state;

     return (
      <div className="railway_line_accordion">
        <div
          className={`accordion__bar${isOpen ? '--on-clicked' : ''}`}
          onClick={() => this.handleClick()}
        >
          {railwayLine.name}
        </div>
        <div
          className={`railway_line_accordion__checkbox-area${isOpen ? '--on-clicked' : ''}`}
        >
          {# railwayLine に紐付く駅を rendering するためにごちゃごちゃ書く}
        </div>
      </div>
    );
  }
}

Accordion.propTypes = {
  initialOpenState: PropTypes.bool.isRequired,
  railwayLine: PropTypes.object.isRequired,
};

export default railwayLineAccordion;

つまり路線 railwayLineprops として渡してあげて、その路線をアコーディオンで表示させるコンポーネントです。

しかしこれはアコーディオンの役割を抽象化できておらず、良くないコード例でした。

再利用性を意識してコンポーネントを切る

今回の例のアコーディオンメニューは、良く見かけるデザインであるため、今後もこのデザインを別の場所で使うことが十分考えられます。

しかし、上記のようにアコーディオンが「何を表示するのか」まで知ってしまうと、別の場所でアコーディオンメニューを駅路線表示以外の目的で使いたいときに、また一からアコーディオンメニューを書くことになります。

アコーディオンメニューの再利用性」を意識して抽象化すると、アコーディオンの役割はあくまで「開閉する」だけと捉えられるので、以下のように書いてあげるとこのコンポーネントの再利用性が高まります。

Accordion.jsx

import React from 'react';
import PropTypes from 'prop-types';

class Accordion extends React.Component {
   constructor(props) {
    super(props);
    this.state = { isOpen: props.initialOpenState };
  }

   handleClick = () => {
    this.setState(prevState => ({ isOpen: !prevState.isOpen }));
  };

   render() {
    const { children, title } = this.props;
    const { isOpen } = this.state;

     return (
      <div className="accordion">
        <div
          className={`accordion__title${isOpen ? '--on-clicked' : ''}`}
          onClick={() => this.handleClick()}
        >
          {title}
        </div>
        <div
          className={`accordion__children${isOpen ? '--on-clicked' : ''}`}
        >
          {children}
        </div>
      </div>
    );
  }
}

Accordion.propTypes = {
  children: PropTypes.node.isRequired,
  initialOpenState: PropTypes.bool.isRequired,
  title: PropTypes.string.isRequired,
};

export default Accordion;

「何を表示させるか」を props で渡すようにして、このアコーディオンコンポーネントは「開閉する」という役割だけ持たせるようにしました。

こうすることで、別の場所でアコーディオンメニューを作りたいときは、このコンポーネントを使って適する titlechildren を渡せば良いだけで良くなります。

終わりに

「再利用性を意識してコンポーネントを切る」というコンポーネント指向の基本ですが、やってみるとなかなか難しく、こうやってアウトプットを繰り返すことでいつか身に付けば良いなぁと思っています。。