React.js 入門

React は MVC の V(ビュー)にあたるフレームワークで、HTMLの表示やイベントなどを管理します。JSX という特殊な文法を使うので、実際に動かす時は webpack などで babel を使用し、ネイティブの文法に変換する必要があります。

webpack についてわからない場合はこちらの記事を参照してください

今回は npm プロジェクトを作成している状態で React.js を使ってみようと思います。動作確認には webpack-dev-server を使います。

React のインストール

ES2015 で React を使用する場合は、以下のモジュールをインストールします。

$ npm install --save-dev react react-dom babel-loader babel-core babel-preset-es2015 babel-preset-react

フォルダ構成は以下のようにしておきます。

sample_app
    ├  package.json
    ├  webpack.config.js
     |
    ├  js
     |    └ index.js
     |
    └  html
          └ index.html

webpack.config.js

module.exports = {
  entry: './js/index.js',
  output: {
    filename: './build/js/index.js'
  },
  module: {
    rules: [
      {
        test: /.js$/,
        loader: 'babel-loader',
        options: {
          presets:['es2015', 'react']
        }
      }
    ]
  }
}

package.json

{
  "name": "sample_app",
  "version": "1.0.0",
  "scripts": {
    "build": "NODE_ENV=production webpack -d",
    "dev": "webpack-dev-server"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "webpack": "^3.5.5",
    "webpack-dev-server": "^2.7.1"
  },
  "dependencies": {}
}

index.html

これで準備完了です。$ npm run dev で webpack-dev-server を起動してください。これから div タグの中に React を使って HTML を描画していきます。

JSX で HTML を描画してみる

index.js を以下のようにし、http://localhost:8080/html/index.html を開いてみましょう。

index.js

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

ReactDOM.render(<div>Hello, World!</div>, document.getElementById('root'))

「Hello, World!」と表示されたら成功です。これが JSX の基本的な書き方になります。

ReactDOM.render(React Element, 描画先のDOM要素)

React Element は React 特有の HTML 要素の定義で、本来の HTML とは書き方が少し異なるということに注意しましょう。

JSXでのコメントアウトは、{/* … */} または {// …} でできます。{ } が必要なので注意しましょう。

JSX で変数を使ってみよう

JSX で変数を使う場合は、{}で囲みます。また、css はオブジェクトで指定する必要があります。className は React Element では要素のクラス属性になります。

index.js

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

const className = 'header'
const css = { color: 'blue' }
const hello = () => { return 'Hello, World!' }

ReactDOM.render(
  <div className={className} style={css}>{hello()}</div>, document.getElementById('root')
)

React Component を作ろう

React Element を他の箇所でも使えるように、関数やクラスで定義しておくと便利です。呼び出される関数やクラスを React Component といいます。

関数で定義する場合

React Element を返り値とする関数を定義します。呼び出す時は、関数名をタグ名として使用します。タグに属性を定義することで、関数では Object として引数で受け取りことができます。コンポーネントの関数名は大文字で始める必要があるので注意しましょう。

index.js

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

function Hello(props) {
  return <div>Hello, {props.name}</div>
}

ReactDOM.render(
  <div>
    <Hello name="Tanaka"/>
  </div>, document.getElementById('root')
)

アロー関数の場合は以下のように書けます。

const Hello = props => (
  <div>Hello, {props.name}</div>
)

クラスで定義する場合

React.Component を継承して、render で React Element を返します。タグに定義された属性は、自身の props プロパティで参照できます。

index.js

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

class Hello extends React.Component {
  render() {
    return <div>Hello, {this.props.name}</div>
  }
}

ReactDOM.render(<Hello name="Tanaka"/>, document.getElementById('root'))

リストを表示しよう

React Element で tr や li のようなリストを表示する時は、リストタグの属性にユニークな key を指定する必要があります。key は React 内部でリスト要素の追加や削除を効率良く行うために利用されています。

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

class UserList extends React.Component {
  render() {
    const names = this.props.names.split(',')
    const listDom = names.map((name, index) => {
        return <li key={index}>{name}</li>
    })
    return <ul>{listDom}</ul>
  }
}

ReactDOM.render(<UserList names='Tanaka, Suzuki' />, document.getElementById('root'))

render の時に コンポーネントをネストして書く方法もあります。その場合、親のコンポーネントは子のコンポーネントを this.props.children で参照することができます。

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

class User extends React.Component {
  render() {
    return <li>{this.props.name}</li>
  }
}

class UserList extends React.Component {
  render() {
    return <ul>{this.props.children}</ul>
  }
}

ReactDOM.render(
<UserList>
  <User name="Tanaka" />
  <User name="Suzuki" />
</UserList>), document.getElementById('root'))

更新される表示情報は state に保存しよう

コンポーネントには state プロパティというものがあり、state にオブジェクトをセットして値を書き換えると、自動的にビューが更新される仕組みになっています。手動で render する必要がないのでぜひ活用していきましょう。ただし、一度 state に値を設定し、以降更新するときは setState() を使わないと自動更新が走らないので注意しましょう。

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

class Timer extends React.Component {
  constructor(props) {
    super(props)

    // constructor 内で state の初期値を設定
    this.state = { count: 0 }

    // 1秒ごとに更新
   setInterval(() => {
      this.setState({ count: this.state.count + 1 })
    }, 1000)
  }

  render() {
    return <div>{this.state.count}</div>
  }
}

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

イベントを実装しよう

React Element に onClick などの React 独自のイベントハンドラを設定します。

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

class Form extends React.Component {
  constructor(props) {
    super(props)
  }
  click(e) {
    console.log('click')
  }
  render() {
    return <button onClick={e => this.click(e)}>クリック</button>
  }
}

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

本来 (e) => this.click(e) というように、最初の e にも括弧が必要なのですが、引数が1つの場合のみ省略できます。

コンポーネントのライフサイクルを理解しよう

コンポーネントが初期化されてから HTML に反映されるまでに、いくつかコールバックが用意されています。

コールバック 説明
constructor() 初期化時
componentWillMount() HTML として描画される直前に呼ばれます
render() 描画する React Element を返します
componentDidMount() HTML として描画された直後に呼ばれます
state 変更時のライフサイクル 下記参照
componentWillReceiveProps(nextProps) 親コンポーネントから新しい props を受け取った時に呼ばれます。必要に応じて setState を呼び自身の描画を更新します。
componentWillUnmount() HTML から削除される直前に呼ばれます

state 変更によるライフサイクル

コールバック 説明
setState() state を変更
shouldComponentUpdate(nextProps, nextState) 描画の更新をするかしないかを true/false で返します
componentWillUpdate(nextProps, nextState) HTML が更新される直前に呼ばれます
render() 上記同様
componentDidUpdate(prevProps, prevState) HTML が更新された直後に呼ばれます

console.log を入れて実際に順番を確かめてみましょう。

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

class Timer extends React.Component {
  constructor(props) {
    console.log('constructor')
    super(props)

    // constructor 内で state の初期値を設定
    this.state = { count: 0 }

    // 1秒ごとに更新
   setInterval(() => {
      this.setState({ count: this.state.count + 1 })
    }, 1000)
  }

  componentWillMount() {
    console.log('componentWillMount')
  }

  componentDidMount() {
    console.log('componentDidMount')
  }

  componentWillUnmount() {
    console.log('componentWillUnmount')
  }

  componentWillReceiveProps(nextProps) {
    console.log('componentWillReceiveProps')
    console.log(nextProps)
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate')
    console.log(nextProps)
    console.log(nextState)
    return true
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate')
    console.log(nextProps)
    console.log(nextState)
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('componentDidUpdate')
    console.log(prevProps)
    console.log(prevState)
  }

  render() {
    console.log('render')
    return <div>{this.state.count}</div>
  }
}

ReactDOM.render(<Timer name="propsです"/>, document.getElementById('root'))

コンポーネント間で連携しよう

子コンポーネントが親コンポーネントにイベントを通知するには、子のコンポーネントに親コンポーネントから関数を渡しておきます。あとは、子が自分のコンポーネントでイベントが発生した時に、親から渡された関数を呼び出すだけです。

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

class Parent extends React.Component {
  childClicked (e) {
    console.log(e.message)
  }
  render() {
    return <Child childClicked={this.childClicked} />
  }
}

class Child extends React.Component {
  clicked(e) {
    this.props.childClicked({ message: 'クリック' })
  }
  render() {
    return <button onClick={e => this.clicked(e)} >クリック</button>
  }
}

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

HTML を操作してみよう

コンポーネントから直接 HTML を操作したい場合は、ref 属性に一意の文字列をセットしておくと、後で refs 属性から取り出して操作することができます。

描画直後にテキストフィールドにフォーカスを当てる例です。

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

class InputForm extends React.Component {
  render() {
    return (
      <input type="text" ref="_text" />
    )
  }
  componentDidMount() {
    console.log(this.refs) // ref属性にセットした要素一覧
    const { _text } = this.refs
    _text.focus()
  }
}

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

別の方法として、render 時に ref 属性に指定したコールバック関数の引数に DOM オブジェクトが入っているので、それを自身のプロパティにセットして使用することもできます。

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

class InputForm extends React.Component {
  render() {
    return (
      <input type="text" ref={ (dom) => { this.buttonDom = dom } } />
    )
  }
  componentDidMount() {
    this.buttonDom.focus()
  }
}

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

以上で React の基本的な説明は終わりです。あとは HTTP 通信部分の実装や、必要に応じて Redux などのフレームワークを導入していきましょう。