Reactで汎用性のあるbuttonコンポーネントの作成 with styled-components

公開:2021/02/11
更新:2021/02/12
5 min読了の目安(約4700字TECH技術記事 2

ボタンはwebページ上に必ずと言っていいほど設置されているパーツです。
似たような見た目のボタンでもクリックしたときの処理は違いますし、スタイリングも様々です。

そんないろんな見た目、役割を担うボタンコンポーネントを作成しましょう。

今回は2つのパターンを用意しました。

  1. コンポーネントへ渡すpropsを利用して実装
  2. styled-componentsの機能を使用して実装

こんなやつを作っていきます。
赤いボタン

この記事での「汎用性」の定義

  • ボタンクリック時の関数を割り振ることができる
  • ボタンのスタイルの使い分けを宣言的に行える
  • ボタンの子要素も自由に設定できる

1.コンポーネントへ渡すpropsを利用して実装

Buttonコンポーネントを宣言するときに、渡されたpropsによって予めスタイルが実装してあるボタンコンポーネントを返す、というものです。
onClickchildなどのprops名でイベントハンドラーやボタンのテキストも渡してあげます。

ここで作成したButtonコンポーネントを宣言するときのイメージはこんな感じです。

<Button style="danger" onClick={handleOnClick} child={赤いボタンです} />

上記の宣言だとこんな感じのボタンが出来上がります。

① BaseButtonコンポーネントを作成

import React from "react";
import styled from "styled-components";

const BaseButton = styled.button`
  text-align: center;
  color: white;
  width: 100%;
  min-width: 100px;
`;

こちらのBaseButtonコンポーネントを継承していろんなスタイルのボタンを作成していきます。

② 実際に使いたいスタイルのボタンを作成

今回は緑色のボタンButtonPrimaryと赤色のボタンButtonDangerを作成します。
styled-componentsで用意されているスタイルの拡張を使用して、BaseButtonのスタイルを拡張します。

import React from "react";
import styled from "styled-components";

const BaseButton = styled.button`
  text-align: center;
  color: white;
  width: 100%;
  min-width: 100px;
`;

// 緑色のボタン(BaseButtonを基に拡張)
const ButtonPrimary = styled(BaseButton)`
  background: green;
`;

// 赤色のボタン(BaseButtonを基に拡張)
const ButtonDanger = styled(BaseButton)`
  background: red;
`;

Buttonコンポーネントに渡ってきたpropsによって作成するボタンのスタイルを決定するロジックを作成

いろんな方法があるかと思いますが、今回はボタンの一覧を表すオブジェクトを作成して実装します。

最終的なButton.jsx
import React from "react";
import styled from "styled-components";

const BaseButton = styled.button`
  text-align: center;
  color: white;
  width: 100%;
  min-width: 100px;
`;

// 緑色のボタン(BaseButtonを基に拡張)
const ButtonPrimary = styled(BaseButton)`
  background: green;
`;

// 赤色のボタン(BaseButtonを基に拡張)
const ButtonDanger = styled(BaseButton)`
  background: red;
`;

// 全ボタンコンポーネント
const buttonStyleLists = {
  default: BaseButton,
  primary: ButtonPrimary,
  danger: ButtonDanger,
};

// propsのstyleTypeでボタンのスタイルを分岐
const Button = ({ styleType, onClick, child }) => {
  const Component = buttonStyleLists[styleType] || buttonStyleLists.default;
  // Component変数に格納したコンポーネントでReact要素を作成
  return <Component onClick={onClick}>{child}</Component>;
};

export default Button;

所感

正直、変数をJSXのタグに使うことができるのは知りませんでした。
ただこれはJSX記法がReact.createElementのシンタックスシュガーということを考えれば当然のことですね。

⚠️変数を大文字にしないとReact要素は生成されないので注意!
上記のコードの例だと、変数名がComponentだと上手くいきますが、componentだとボタンは描画されません。
JSXを深く理解する - React

2. styled-componentsの機能を使用して実装

こちらの方法はstyled-componentsの機能に依存します。

公式ドキュメントに記載があります。
※拙い和約失礼します。

The styled method works perfectly on all of your own or any third-party component, as long as they attach the passed className prop to a DOM element.

styledメソッドはあなたが作成したコンポーネントや他のどのコンポーネントでも、DOMエレメントに className が渡されている限り動作します。

① BaseButtonコンポーネントを作成

Button.jsx
import styled from "styled-components";

const BaseButton = styled.button`
  text-align: center;
  color: white;
  width: 100%;
  min-width: 100px;
`;

これは前回の1.コンポーネントへ渡す props を利用して実装と同じです。
このコンポーネントを基に他のボタンのスタイルを行っていきます。

② Buttonコンポーネントを完成させる

<BaseButton>コンポーネントとしてexportしてしまいます。
この際に気をつけるべきところは、classNameを渡してあげることです。
classNameを渡してあげないと、先ほど説明したsytled-componentsでのコンポーネントの追加スタイリングができなくなってしまいます。

Button.jsx
import React from "react";
import styled from "styled-components";

const BaseButton = styled.button`
  text-align: center;
  color: white;
  width: 100%;
  min-width: 100px;
`;

// classNameを記載する
export const Button = ({ children, className, onClick }) => {
  return <BaseButton className={className} onClick={onClick} >{children}</Button>;
};

③ Buttonコンポーネントを宣言する時にコンポーネントのスタイルを拡張する

試しにApp.jsで実装してみます。
呼び出す時にonClickchildrenも渡してあげましょう。

App.js
import logo from './logo.svg';
import './App.css';
import Button from './Button'; // 先ほどのButtonコンポーネントをimport
import styled from 'styled-components';

// Buttonコンポーネントのスタイルを拡張して新しいコンポーネントを作成
const DangerButton = styled(Button)`
  background: red;
`;

function App() {
  return (
    <div className='App'>
      <header className='App-header'>
        <DangerButton onClick={() => console.log("Hello")} children={"text"}></DangerButton>
      </header>
    </div>
  );
}

export default App;

所感

ボタンのコンポーネントを宣言するたびにスタイルを設定しないといけないので、プロジェクトの規模が大きくなってきたら管理が大変になりそうですね。。。

まとめ

今回は2つの方法でボタンコンポーネントを作りました。
間違いなどがあったら是非コメントで教えてください。