2015.12.07

React と jQuery を共存させる


GMO スマートリザーブに React を導入

フロントエンドのトレンドを追いかけている D.M. です。
2015年の初夏ごろに次世代システム研究室が GMO スマートリザーブの立ち上げをサポートしました。
http://www.gmo-c.jp/service/smart_reserve.html
こちらは店舗管理者がお店の予約を管理するためのシステムですが管理画面は Single Page Application となっており、 JS 部分に React を導入しています。今回はその案件で問題になった React と jQuery の共存について書いてみようと思います。

React を選定した理由

React については以前記事でまとめており概要は把握していました。
http://recruit.gmo.jp/engineer/jisedai/blog/react-refactoring-legacy-javascript/

2015年 JS フレームワークは群雄割拠の様相を呈している中、実質的には Angular が一番人気です。そこで選定前に React と Angular の下馬評を比べてみました。

・Angular
メリット① ここ 2, 3 年は Angular が1番人気で国内実績も多い状況。
メリット② MVC 全部盛りで高機能。
デメリット① Angular 1 系は終わり Angular 2 がそろそろ出る(しかも仕様がぜんぜん違う)。
デメリット② React に比べると少しだけ遅い。

・React
メリット① MVC の V しか機能がないが、シンプルなため学習コストが低い。
メリット② 2015 年には国内でも広く認知されるようになりエンジニア間で注目度は高い。
メリット③ React は JSX の仕組みにより JS と HTML の分離を実現しているのでデザイナーでも触りやすい。
デメリット① 発展途上でヴァージョンアップも頻繁にある。

今回はスケジュールが厳しい案件だったこともありシンプルで理解しやすいものが必要でだったため React が当選となりました。

デザイナーからあがってきた苦情

実際 React のメリット③は少し無理がありました。 JSX は JS と HTML が分離していますが、コンポーネントタグが完全に HTML ではなく、 JS 力の低いデザイナーには手を出すことが困難なものです。この点 Angular のほうはそもそも HTML ベースなので、他の HTML 編集ツールと一緒に使うこともでき、デザイナー親和性は高い部分があると思っています。
また担当デザイナーから私が調査段階で記述していた「 React は jQuery とも共存できる」という点について指摘がありました。
「 jQuery UI と一緒に使うと動作しないんですけど!!!」
はい、確かに。 React も jQuery もお互い DOM を触りにいくので、お互いが何をしているか理解していないと DOM が壊れてしまいます。ここは必要に応じて実装者が制御しなければなりません。

React と jQuery 共存の方針

「そもそも一緒に使うな」という意見もありますが、状況によっては必要になってしまうのがシステム屋あるあるです。
本家サイトにも jQuery with React というディスカッションの issue が上がっています。
https://discuss.reactjs.org/t/jquery-with-react/683

一番重要なところはこの1文です。
・ run jQuery only in the lifecycle methods where the DOM node is present or will be removed: componentDidMount, componentWillUnmount, componentDidUpdate.
「 jQuery は DOM が現れたり削除されたりするライフサイクルメソッド( componentDidMount, componentWillUnmount, componentDidUpdate )の中でだけ実行しなさい。」
これは端的に併用の方針を表しています。以下でライフサイクルと導入について具体的に掘り下げていきます。

jQuery のライフサイクルと React のライフサイクル


jQuery UI Datepicker

これは非常によく使われるカレンダーライブラリです。最近では HTML5 の input タグ type=”date” が動作するブラウザも一部ありますが例によって全ブラウザでは利用できないので、この Datepicker を適用するケースは依然としてあると思います。 React と同時に使う可能性が高い jQuery ライブラリです。

公式サイト
https://jqueryui.com/datepicker/
blog_datepicker

※ input type=”date” に関するブラウザのサポート状況
http://caniuse.com/#feat=input-datetime

Datepicker は以下のとおり任意のinput タグにバインドしてやるだけで動作します。非常にシンプルです。
<script>
   //画面描画後に呼ばれる jQuery の処理
    $(document).ready(function(){
        // datepckr に機能をバインドする
        $("#datepckr").datepicker();
    });
</script>
<input type="text" id="datepckr" />
このライブラリについては実装者が任意で1つの生成タイミングを決定できます。通常は画面生成時が唯一のライフサイクルです。

$(document).ready(function(){}) : 画面の初期処理。


React のライフサイクル


React コンポーネントの生成から消滅までのライフサイクルは代表的なもので以下とおりです。

getInitialState : コンポーネントの初期化時に1回だけ呼ばれます。
componentWillMount : render 前に呼ばれます。
render : 描画処理です。
componentDidMount : render 後に呼ばれます。
componentWillUpdate : State 変更後の render の前に呼ばれます。
componentDidUpdate : State 変更後に呼ばれます。
componentWillUnmount :コンポーネント削除前に呼ばれます。


こちらの記事に出ている図が非常に詳しいです。
blog_react
引用元: http://qiita.com/kawachi/items/092bfc281f88e3a6e456

壊れてしまう原因

Datepicker を $(document).ready(function(){}) の画面ロード直後の処理で利用していると確かに動作しません。理由は React は state の値が変更されると再描画の render() が実行される仕様となっているため、 Datepicker が紐づいていた DOM が初期化されてしまいバインドが切れてしまうことです。結論としては上記ライフサイクルに合わせて Datepicker を再度紐付ける処理が必要となります。

以下は実際の案件で実装した予約フォーム ReserveForm コンポーネントと Datepicker を設定する InputDate コンポーネントの抜粋です。

//InputDate コンポーネントを React で定義しています。
var InputDate = React.createClass({
    render : function(){
         return (
                    <dd>
                    <input
                        value={this.props.date}
                        name="" 
                        className="input-date" 
                        type="text"
                        onChange={this.changeDate}/>
                    </dd>
        );
    },
    changeDate: function(event) {
                //親コンポーネントに変数渡すなどの処理(割愛)
    },
    //HTML描画 render() 後の処理
    componentDidMount: function () {
             self = this;
             //render 後なら DOM をいじり放題なのでここで設定しています。
             $('.input-date').datepicker(
                    {
                        //Datepicker 初期化のための値
                        dateFormat: 'yy-mm-dd',
                        //選択時のイベント処理
                        onSelect: function(dateVal) {
                           //親コンポーネントに変数渡すなどの処理(割愛)
                        }
                    }
                );         
     }
});

var ReserveForm = React.createClass({
    render: function () {
        return (
                   // ・・・ 以下は予約フォームの日付入力部分。この前に予約人数入力などもありますが割愛します。
                   <dl>
                        <dt>日にち(From)</dt>
                        // 日付加工などの処理が含まれた日付入力コンポーネント。ここに Datepicker がいます。
                        <InputDate date={初期の日付の値} />
                   </dl>
                   // ・・・ 予約フォームのHTML後半部分は割愛します。
        );
   }
});

タイミングとしては componentDidMount() すなわち render() が一度終了したタイミングで再度 Datapicker の紐付けを実行しています。今回は書いていないのですがもし画面で stete の更新が入る場合は componentDidUpdate() についても Datapicker 紐付けが必要です。 React が書き換えた直後に jQuery UI を再設定することで共存を実現しています。

イベントハンドリングあれこれ


jQuery の DOM 操作以外にもイベントのハンドリングについては一癖あります。
React 公式ページにはブラウザで発生するスクロールやフォーカス、クリップボードのコピーやキーボード押下といったイベントのハンドリングについての扱いが記述されています。
http://facebook.github.io/react/docs/events.html
Clipboard

またWindowのリサイズイベントの扱いについてもノウハウが公開されています。
http://facebook.github.io/react/tips/dom-event-listeners.html

React にてスマートフォンのタッチイベントを有効にするには以下の設定が必要です。
React.initializeTouchEvents(true);

jQuery に関連して CSS アニメーションについても ReactCSSTransitionGroup といった特殊なタグを利用することで実現できます。
https://facebook.github.io/react/docs/animation.html

最後に


React の実践的な利用方法についてまだ日本語ドキュメントが充分でない状況があるため、現場で利用できる Tips を見つけ次第また報告していきたいと思います。

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。