アプリを作ってReactの基礎を学んでみた

/

導入

前回までに何度かReactの開発ツールであるCreateReactAppの使い方を取り上げたので、今回は簡単なCounterアプリを作ってReact自体の基礎的な使い方について調べてみました。

ReactCreateApp vol.1 – Koltatt
ReactCreateApp vol.2 – Koltatt

環境

  • ubuntu16.04
  • Create React App 0.6.0
  • React 15.4.1

JavaScriptはES6(ES2015)を利用して記述しています。

App

See the Pen Counter by koltatt (@koltatt) on CodePen.

Reactとは

ReactはFacebook社の開発したUIを作成するためのJavaScriptのライブラリです。
いわゆるMVCモデルのViewだけに特化したライブラリのため、今回のアプリのようにidやclassで紐付けるだけでWebページにUIを挿入する事が出来ます。

React

React(Package)

React(Package)はReactでAppを作成するのに必要なパッケージの一つです。
パッケージとしてのReactはReactライブラリのエントリポイントになるトップレベルのAPIで、UIを作るのに必要なコンポーネントを制作するのに利用します。

CreateReactAppではプロジェクトの雛形を作成した段階でライブラリのメンバーを指定してReactが読み込まれています。

import React, { Component } from 'react';

Component

Reactはコンポーネントと呼ばれる独立したパーツの組み合わせによってUIを構成します。

ES6ではReactのサブクラスであるReact.Componentを継承する事コンポーネントを作成する事が出来ます。

import React, { Component } from 'react';

class Conuter extends Component {
  ...
}

今回の例ではReact.Componentを継承してCounterクラスを作成しています。

render

renderは描写先のDOMからコンポーネントを呼び出すためのメソッドで、propsやstateなど(後述)のデータの値を検証して一つのReact elementを返します。

コンポーネントをDOMに描写するためにはrenderによって値を返す必要があるので、DOMに挿入するコンポーネントを作成する際にはrenderは必須の要素になります。

The render() method is required.

When called, it should examine this.props and this.state and return a single React element. This element can be either a representation of a native DOM component, such as <div />, or another composite component that you’ve defined yourself.
render – React.Component/React

Props

propsはコンポーネントがデータを管理する際に利用するオブジェクトです。
コンポーネントの作成時にpropsを変数のように扱う事で任意の値を引き渡し上でコンポーネントを呼びだす事が出来ます。

propsの値はコンポーネントの呼び出し後に変更出来ないため、コンポーネントの持つ値の更新を行いたい場合にはもう一つのデータ保持用のオブジェクトであるstateを使う必要があります。

Whether you declare a component as a function or a class, it must never modify its own props.

React is pretty flexible but it has a single strict rule:
All React components must act like pure functions with respect to their props
props are Read-Only – Components and props/React

Propsの値の指定

propsの値はコンポーネントの作成時に指定します。

< Counter title="Counter App" />

値はdefaultPropsメソッドを使う事で、コンポーネント側でデフォルトの値を設定することも可能です。
デフォルトの値を設定した場合にはコンポーネントの呼び出し時に値を入力すればその値がpropsに入力されますが、なにも入力せずにコンポーネントを呼び出した時にはデフォルトの値が入力されます。

Counter.defaultProps = {
  title: "Write your app title here"
};

値の呼び出し

設定したpropsの値は{}でくくる事でコンポーネントのレンダリング処理の中で出力する事が出来ます。


<h1>{ this.props.title }</h1>

今回のAppではブラウザで表示するAppのタイトルをpropsを使ってコンポーネントの作成時に定義しています。

state

Reactでコンポーネントがデータを管理するためのもうひとつの方法としてstateがあります。
propsと違いstateの値は後から変更する事が可能です。

constructor

stateの値はクラス定義の際にcostructor()を呼び出して初期化します。
stateの初期値は連想配列を使って渡します。

constructor(props) {
    super(props);
    this.state = { counter: 0 }
}

stateの値の更新

stateは値を変更する事が可能ですが、その際に各stateに直接代入するのではなくsetState()を呼び出して値を更新します。

count(e) {
    const num = Number(e.currentTarget.getAttribute('data-number'));
    this.setState({ counter: this.state.counter + num });
}

値の呼び出し

stateの値の呼び出しもpropsと同様に{}を使用します。



{ this.state.counter }

AppのCounterクラスでは各ボタンがクリックされてメソッドが呼び出されるたびに共通の値を更新する必要があるため、クラスでstateを定義して値の変更を実行しています。

またCounterクラスに定義されたメソッドはstate.counterを更新するための数値を各ボタンに設定されたカスタムデータ属性の値から取得しています。
All Supported HTML Attributes – DOM Elments/React

ReactDOM

ReactDOMはReactのAppを作るのに必要なもうひとつのパッケージでReactのコンポーネントを出力先となるDOMに描写するためのメソッドを提供してくれます。

ReactDOMはReact v0.14.0よりReactから独立したパッケージとして分割されました。
CHANGELOG – React/github

別々のパッケージのためReactDOMを使用する際にはReactとは別に呼び出してやる必要があります。

import React from 'react';
import ReactDOM from 'react-dom';

render()

ReactDOMのrenderメソッドはdocument.getElementById()などによってHTMLに紐付けられたcontainerにReact elementを出力します。

ReactDOM.render(
element,
container,
[callback]
)
render – ReactDOM/React reference

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './Counter';
import './index.css';

ReactDOM.render(
  <Counter title="Counter App" />,
  document.getElementById('root')
);

今回のAppではpropsに値を渡してからCounterクラスによるコンポーネントを呼び出し、コールバックによって値と共に帰ってきたコンポーネントをHTMLとidによって紐付けられたDOMに出力しています。
render – ReactDOM/React reference

JSX

今回作成したAppのJavaScript内では以下のような、一見HTMLタグの様にに見えるコードが使われています。

<button onClick={ this.count1.bind(this) } >
  + 1
</button>

これはJSXと呼ばれるJavaScript内でXMLのような文法が利用出来る拡張ライブラリを利用して書かれたコードで、ReactではJSXを使う事でHTMLタグと同じ表記を使ってコンポーネントの構造を表現する事が出来ます。
Introducing JSX -React

例えばJSXを使うと通常ではReactプログラムの中で以下のように書なければならないReact.createElementの部分を

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  render() {
    return(
      React.createElement('h1',{ className:'greeting' },"Hello John!!")
    );
  }
}

ReactDOM.render(
<App />,
  document.getElementById('root')
);

このようにHTML風に表現する事が出来るようになります。

class App extends React.Component {
  render() {
    return(
    
<h1 className="greeting">
      Hello Jhon!!
   </h1>

    );
  }
}

JSXはJavaScriptの文法を拡張するためのライブラリなので、Reactのコードを書く際に必ず使用しなければならないものではありません。
またセレクタとしてクラスを使いたい場合にはキーワードとしてclassNameを使う必要があるなどのHTMLとの違いもありますが、HTMLタグさえ理解していれば出力先のDOMで描写されるコードとほぼ同じ文法で書くことが出来るため、Reactのコードを書く際にはJSXの利用が推奨されているようです。

イベントハンドラの問題

今回記事に載せるサンプルのためにCodePenでES6を使ってReactのコード書いたのですが、この問題で少し詰まりました。

ReactではonClickに{}で括った関数を渡すとthisが自動的に評価されてコンポーネントのインスタンスにbindしてくれると言われていたので、そう期待して実装してみたところ上手く動いてくれませんでした。

色々な所でこれで問題ないと書いてあったので、困って調べたところES6を使ってReact.Componentを継承したクラスのメソッドによって関数を定義した場合にはthisが自動的に評価されないためbindで明示する必要があるようです。

Handling Events – React

原因がわかったのでコードを修正しました。
数が少ないので直接onClickに関数をbindして渡しても良かったのですが、今回はconstructorの中であらかじめ関数を定義してみました。

constructor(props) {
  super(props);
  this.state = { counter: 0 }
  this.count = this.count.bind(this);
  this.sub   = this.sub.bind(this);
  this.reset = this.reset.bind(this);
}

これで問題は無くなりましたが、ES6を使った事で書かないといけないコードが増えてしまうのはちょっと残念です。

あとがき

Reactは中々個性の強いライブラリなので、勉強中のつたない知識で概要を説明しようとすると難しい所が多く苦労しました。
ただMVCのViewだけしか扱わないのでアプリケーションとしてはコンパクトなサイズから作れる事と、UIという事で形にしやすいという事から実際に手を動かしてコードを書いてみるとドキュメントを読んだ時に感じる程とっつきづらくはないという印象も受けました。

そのためドキュメントを理解する事も大切ですが、Reactの場合習うより慣れろの精神で解ったところから実際に手を動かしてみるというのも効果的な勉強法だと思います。