React.js で Canvas Element を使う

以下のような Component を作る。

```js
import React, { Component, PropTypes } from 'react';

export default class CanvasComponent extends Component {
  componentDidMount() {
    this.updateCanvas();
  }

  componentWillReceiveProps(nextProps) {
    if (this.props !== nextProps) {
      this.updateCanvas();
    }
  }

  componentDidUpdate() {
    this.updateCanvas();
  }

  updateCanvas() {
    const { canvas } = this;
    const context = canvas.getContext('2d');
    this.props.updateCanvas(context);
  }

  render() {
    return <canvas ref={(e) => { this.canvas = e; }} width={this.props.width} height={this.props.height}></canvas>;
  }
}

CanvasComponent.propTypes = {
  width: PropTypes.number.isRequired,
  height: PropTypes.number.isRequired,
  updateCanvas: PropTypes.func.isRequired,
};
```

で、呼び出し元の `updateCanvas()` メソッドの中で Canvas の描画処理を書く。

```js
import React, { Component } from 'react';
import CanvasComponent from './CanvasComponent';

const CANVAS_WIDTH = 140;
const CANVAS_HEIGHT = 30;

export default class TestComponent extends Component {
  constructor() {
    this.state = {
      text: 'text',
    };
  }

  render() {
    const canvasProps = {
      width: CANVAS_WIDTH,
      height: CANVAS_HEIGHT,
      updateCanvas: (context) => {
        context.fillStyle = 'red';
        context.font = '24px Arial';
        context.textAlign = 'left';
        context.textBaseline = 'top';
        context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
        context.fillText(this.state.text, 0, 0);
      },
    };

    return (
      <div>
        <input value={this.state.text} onChange={elm => this.setState({text: elm.target.value})} />
        <CanvasComponent {...canvasProps} />
      </div>
    );
  }
}
```

つまり、`CanvasComponent` のなかで更新があるか、Props に更新があった時に Canvas の描画処理を走らせれば良い。

なお、この手法は Canvas-Element に限った話ではなく、例えば jQuery-Plugin でも下記のようにすれば React で扱うことができる。

```js
import React, { Component, PropTypes } from 'react';
import jQuery from 'jquery';
import 'fullcalendar';

export default class FullCalendar extends Component {
  componentDidMount() {
    const { calendar } = this.refs;
    jQuery(calendar).fullCalendar({
      timezone: 'JST',
      timeFormat: 'YYYY/M/D H:mm',
      header: {
        left: 'today prev next',
        center: 'title',
        right: 'month agendaWeek agendaDay',
      },
      firstDay: 1,
      eventClick: event => {
        if (typeof this.props.onClickEvent == 'function') {
          this.props.onClickEvent(event);
        }
      },
      dayClick: date => {
        jQuery(calendar).fullCalendar('changeView', 'agendaDay');
        jQuery(calendar).fullCalendar('gotoDate', date);
      },
    });
  }

  componentWillReceiveProps(nextProps) {
    const { calendar } = this.refs;
    if (nextProps.events !== this.props.events) {
      jQuery(calendar).fullCalendar('removeEvents');
      jQuery(calendar).fullCalendar('addEventSource', nextProps.events);
    }
  }

  componentWillUnmount() {
    const { calendar } = this.refs;
    jQuery(calendar).fullCalendar('destroy');
  }


  render() {
    return <div ref="calendar"></div>;
  }
}

FullCalendar.propTypes = {
  events: PropTypes.array,
  onClickEvent: PropTypes.func,
};
```