30分間React入門「いいねボタン」作成チュートリアル


30分間React入門「いいねボタン」作成チュートリアル

「今日からReactを始めたい」

「とりあえずReactで何か動かしてみたい」

ここは、そういった方が、Reactを始めみるためのチュートリアルです。

今回は、Reactを使ってFacebookのいいねボタンのようなコンポーネントを実装していきます。

今回作る「いいねボタン」のデモ

チュートリアルの目標

  • Reactのコンポーネントを実装できるようになる
  • Reactコンポーネントのイベントをハンドリングできるようになる
  • Reactコンポーネントの状態(state)を実装できるようになる
  • Reactの雰囲気をつかめる!

このチュートリアルについて

なお、このチュートリアルは、下記のバージョンで動作確認しています。

  • npm 2.14.2
  • react 0.14
  • react-dom 0.14
  • webpack 1.12.2
  • babel-core 5.8.25
  • babel-loader 5.3.2

このチュートリアルで作成するコンポーネントの完成形はGitHubからチェックアウトできます。

また、今回はReactだけでコンポーネントを作成し、Fluxは使用しません。

下準備

下準備: プロジェクト新規作成

ひとまずディレクトリを作って、

mkdir react-like-button
cd react-like-button

プロジェクトを初期化します。

npm init

下準備: 必要ツールのインストール

開発に必要なツールをnpmでインストールしておきます。

npm install --save-dev webpack babel-loader

今回インストールするのは次の2つです。

  • webpack: JSを依存関係を解決しつつ、ひとつのスクリプトにビルドしてくれるツール。
  • babel-loader: ES6、ES7のトランスパイラBabelをwebpackで使えるようにするアドオン。

これらのツールの説明は省略します。詳しくは次の記事をご参考に。

下準備: webpackの設定

webpack.config.jsという名前でwebpackの設定ファイルを作ります。

touch webpack.config.js
module.exports = {
  entry: __dirname + "/src/main.js",
  output: {
    path: __dirname + "/dist",
    filename: "like-button.js"
  },
  module: {
    loaders: [
      {test: /\.js$/, loader: "babel-loader?stage=0"}
    ]
  }
};

少し説明すると、entryoutputでは「src/main.jsをコンパイルし、dist/like-button.jsを生成する」設定になっています。
loadersは拡張子がjsのものにbabel-loaderを適用する設定、つまりES6をJSに変換するようになっています。
stage=0はES7も有効にするBabelの設定です。

ソースコード置き場と、ビルドされたJSが生成される場所を作ります。

mkdir src dist

下準備: コンポーネントを実装するファイルを作る

まずは、ボタンをコーディングするファイルを作りましょう。

touch ./src/main.js

webpackでちゃんとビルドされるかを確認したいので、
ひとまず、中身はconsole.logにしておき、読み込まれていることが確認できるようにしておきます。

console.log("OK");

下準備: 確認用ページを作る

つぎに、ビルドされたJSの動作確認をするHTMLを作ります。

touch dist/index.html

中身はこんなかんじ。

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Reactで作った「いいねボタン」のデモ (Flux不使用)</title>
</head>
<body>
  <h1>Reactで作った「いいねボタン」のデモ (Flux不使用)</h1>
  <script src="like-button.js"></script>
</body>
</html>

下準備: webpackでビルドを試す

ビルドするために、package.jsonにwebpackのビルドタスクを定義しておきます。

{
...
  "scripts": {
    ...
    "build": "rm -rf dist/*.js && webpack",
    "watch": "rm -rf dist/*.js && webpack -w"
  },
...
}

ひとつ目は単発でビルドするタスクの定義、ふたつ目はソースコードが変更されたら自動でビルドが走るタスクです。
開発時はふたつ目を使います。

ビルドタスクが走るか試してみましょう。

npm run build

dist/like-button.jsが生成されたでしょうか?

最後に、確認ページを開き、コンソールに「OK」と出ているのが確認できれば、ビルドも実行もうまくいったことになります。

open dist/index.html

下準備は以上です。

「いいね!」ボタンを作る

ここからは「いいね!」ボタンの開発に入っていきます。

Reactをインストール

Reactをインストールします。

npm install --save react react-dom

確認用HTMLdist/index.htmlを変更して、Reactコンポーネントをマウントする場所をHTMLに書き込みます。

...
  <h1>Reactで作った「いいねボタン」のデモ (Flux不使用)</h1>
  <div id="like-button"></div><!-- ここにコンポーネントをマウント -->
  <script src="like-button.js"></script>
...

like-button.jsが読み込まれると、このid="like-button"にいいねボタン コンポーネントが描画されます。

まずは、最もシンプルなコンポーネントをmain.jsに書いてみましょう。
<span>を出力するだけのコンポーネントです。

import React from "react";
import ReactDom from "react-dom";

class LikeButton extends React.Component {
  render() {
    return (
      <span>いいねボタン予定地</span>
    );
  }
}

ReactDom.render(
  <LikeButton />,
  document.getElementById("like-button")
);

コードに直接書かれた<span>タグなどはJSXと呼ばれます。HTMLに見えますがXMLです。
XMLなので閉じタグを省略できない点に注意してください。
React.Componentを継承し、renderメソッドを実装すると、Reactコンポーネントになります。
Reactコンポーネントは、クラス名をそのままJSXのタグに使うことができます。

HTMLとJSXの違いについてはドキュメントに説明があります。

webpackのwatchタスクを起動して、ビルドを走らせます。

npm run watch

ビルドが終わったら、確認用HTMLを読み込みしなおしてみて、「いいねボタン予定地」が表示されていればOKです。

react-30min-introduction1

ボタンのデザインを作りこむ

「いいね!」ボタンはFacebookのLikeボタンのようにボタンと吹き出しがセットのものを作ります。

このチュートリアルでは、Facebook風のデザインを作る上で、html – how to draw the # of likes bubble like what is on the right side of the facebook like button – Stack Overflowを参考にしています。

先ほどのLikeButtonの中身を書き換えて、JSXとスタイルを書きます。

class LikeButton extends React.Component {
  styles() {
    return {
      container: {
        fontFamily: "helvetica, arial, 'hiragino kaku gothic pro', meiryo, 'ms pgothic', sans-serif",
        fontSize: 11
      },
      like: {
        display: "inline-block",
        background: "#3b5998",
        padding: "0px 5px",
        borderRadius: 2,
        color: "#ffffff",
        cursor: "pointer",
        float: "left",
        height: 20,
        lineHeight: "20px"
      },
      likeHover: {
        background: "#444"
      },
      counterBefore: {
        display: "block",
        float: "left",
        width: 6,
        height: 6,
        background: "#fafafa",
        marginLeft: "-12px",
        borderRight: 10,
        transform: "rotate(45deg)",
        WebkitTransform: "rotate(45deg)",
        marginTop: 6,
        borderLeft: "1px solid #aaa",
        borderBottom: "1px solid #aaa"
      },
      counter: {
        display: "block",
        background: "#fafafa",
        boxSizing: "border-box",
        border: "1px solid #aaa",
        float: "left",
        padding: "0px 8px",
        borderRadius: 2,
        marginLeft: 8,
        height: 20,
        lineHeight: "20px"
      }
    };
  }

  render() {
    const styles = this.styles();
    return (
      <span style={styles.container}>
        <span style={styles.like}>いいね!</span>
        <span style={styles.counter}>
          <span style={styles.counterBefore}>{" "}</span>999
        </span>
      </span>
    );
  }
}

ReactではclassName属性を使えばスタイルシートをCSSに追い出すこともできますが、
ウィジェットとして設置可能にすることまで考えて、ここではインラインスタイルで書きます。
Reactのインラインスタイルは、JavaScriptのオブジェクトで表現する点が、普通のHTMLとは異なります。

カーソルが乗った時にボタンの色を変える

いいねボタンにカーソルが乗った時に、背景色が変化するフィードバックを実装します。

CSSであれば:hoverを定義するだけですが、
Reactのインラインスタイルでは、onMouseEnterイベントとonMouseLeaveを使って動的にスタイルを変更する方法で実装します。

今のLikeButtonコンポーネントに手を加えて、カーソルが乗っているかの状態とイベントハンドラを追加します。

class LikeButton extends React.Component {
  constructor(props) {
    super(props);
    // コンポーネントにカーソルが乗ているかどうかの状態を持たせる
    this.state = {
      hovered: false
    }
  }

  styles() { ... }

  // カーソルが乗った時に状態を変更するイベントハンドラ
  onMouseEnter() {
    this.setState({hovered: true});
  }

  // カーソルが外れた時に状態を変更するイベントハンドラ
  onMouseLeave() {
    this.setState({hovered: false});
  }

  render() {
    const styles = this.styles();
    console.log(this.state); // 状態をログに出す

    // ボタンに onMouseEnter と onMouseLeave のイベントハンドラを割り当てます
    return (
      <span style={styles.container}>
        <span
          style={styles.like}
          onMouseEnter={::this.onMouseEnter}
          onMouseLeave={::this.onMouseLeave}>いいね!</span>
        <span style={styles.counter}>
          <span style={styles.counterBefore}>{" "}</span>999
        </span>
      </span>
    );
  }
}

ビルドが走ったら、確認用HTMLにて、ボタンにカーソルを乗せたり外したりしてみて、
コンソールにコンポーネントの状態を出ているか確認してください。

react-30min-introduction3
ここまでできたら、this.state.hoveredの状態に応じてスタイルを切り替えれば、背景色を変更できます。
renderメソッドを変更してみましょう。

class LikeButton extends React.Component {
  ...
  render() {
    const styles = this.styles();
    // 状態に応じてスタイルを変更する
    const likeStyle = this.state.hovered ? {...styles.like, ...styles.likeHover} : styles.like;
    return (
      <span style={styles.container}>
        <span
          style={likeStyle}
          onMouseEnter={::this.onMouseEnter}
          onMouseLeave={::this.onMouseLeave}>いいね!</span>
        <span style={styles.counter}>
          <span style={styles.counterBefore}>{" "}</span>999
        </span>
      </span>
    );
  }
}

ちなみに、{...styles.like, ...styles.likeHover}はES7でふたつのオブジェクトをマージする構文です。

色が変わるようになったでしょうか?

react-30min-introduction4

ボタンを押して、数を増やしたり減らせるようにする

最後に、ボタンを押したときに、数を増減する処理を追加します。
FacebookのLikeボタンに習い、次の仕様を実装します。

  • いいね済みの場合: ボタンを押すといいね解除、カウンタを-1する。
  • いいねがまだの場合: ボタンを押すといいね状態になり、カウンタを+1する。

カウント数と押したかどうかの2つの状態を、stateに追加します。
そして、クリックしたときのイベントハンドラと追加し、
そのイベントハンドラをボタンと紐付けます。

import React from "react";
import ReactDom from "react-dom";

class LikeButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hovered: false,
      count: 999,   // カウント数の状態を追加
      liked: false  // 押したかどうかの状態を追加
    }
  }

  styles() { ... }
  onMouseEnter() { ... }
  onMouseLeave() { ... }

  // クリックしたときのイベントハンドラ
  onClick() {
    this.setState({
      count: this.state.count + (this.state.liked ? -1 : 1),
      liked: !this.state.liked
    });
  }

  render() {
    ...
    // this.state.likedとthis.state.countを追加し、
    // onClickイベントハンドラを紐付ける
    return (
      <span style={styles.container}>
        <span
          style={likeStyle}
          onMouseEnter={::this.onMouseEnter}
          onMouseLeave={::this.onMouseLeave}
          onClick={::this.onClick}>{this.state.liked ? "✔ " : ""}いいね!</span>
        <span style={styles.counter}>
          <span style={styles.counterBefore}>{" "}</span>
          {this.state.count}
        </span>
      </span>
    );
  }
}

この変更で、ボタンを押した時に、いいね数が変化するようになったかと思います。

react-30min-introduction5

おわり

いかがでしょうか?

以上で「いいね!」ボタンをReactで作るチュートリアルはおしまいです。

このチュートリアルで作成したコンポーネントの完全版はGitHubのsuin/react-like-button-exampleにて確認できます。

次は、もっと複雑なコンポーネントを実装するチュートリアルを紹介したいと思います!

質問や分からないことがあれば、お気軽にコメントしてください(^-^)