MilkcocoaでBaaSを体験!~バックエンドの仕組みと使い方~

第9回React.jsとmilkcocoa.jsでTwitterのリプライ通知を作ろう!

今回の目的

実践的なBaaSの利用法を学ぼう!

やること

React.jsとmilkcocoa.jsでTwitterライクなリプライ通知機能を実装してみます。

準備するもの

  1. reactmilk-tutorialをローカルに持ってきてください。
  2. (4月30日以前に記事を読んだ方)Firebaseのアカウントを作って、appを1つ作成してください(MilkcocoaがMQTT対応でメンテナンス中のため⁠⁠。
  3. (5月1日以降に記事を読んだ方)Milkcocoaのアカウントを作って、appを1つ作成してください。

コードを読むだけで理解できる方は以下を読み進める必要はありませんが、順を追ってReactの使い方とBaaSの使い方の解説を入れて行きます。

手順

Reactの動作確認

gulpの準備

npm i gulp gulp-webserverしてください。

Reactの動作環境を作る

  <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react.js"></script>

  <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/JSXTransformer.js"></script>

  <script src="jsx/main.jsx" type="text/jsx;harmony=true"></script>

react本体とjsxコンパイラはcdnでホスティングされているので、練習にはそれを使いましょう。.jsxファイルを読み込んで、typeとしてtext/jsx;harmony=trueを付けましょう。これで実行時にES6とJSXを利用可能な状態として実行時に処理してくれます。

Milkcocoaの動作確認

Milkcocoaはちょうどメンテナンス期間で5月1日まで新規アプリケーションの作成を停止しているので、今日のところは筆者が作ったOSSのMilkcocoa互換インターフェースを使いましょう。

  <script src="https://cdn.firebase.com/js/client/2.2.3/firebase.js"></script>

  <script src="https://cdn.pubnub.com/pubnub-dev.js"></script>
  <script src="lib/milkcocoa.js"></script>
  <script src="lib/load-milkcocoa.js"></script>

PubNubとFirebaseを無理矢理ひっつけて作ったので、ところどころ動作が不安定かもしれません。

すぐに本家Milkcocoaは復活するので、load-milkcocoa.jsの記述を書き換えてあげましょう。

load-milkcocoa.js
(function(global){
  var milkcocoa = new MilkCocoa("psuedo-twitter", 
"pub-c-3110c846-acee-4a93-b334-605c31524237", 
"sub-c-8472c902-d950-11e4-895c-02ee2ddab7fe");
  global.milkcocoa = milkcocoa;
  return global;
}(window));

フィードのビューの実装

tweetが表示されるフィードについて、実装を省いた振る舞いだけを書くと以下のような感じになります。

基本的に描画・イベントリスナがほとんどを占めています。

feed_component.jsx
(function(global){
  var FEED = React.createClass({
    render(){},
    onInputKeyPress(e){},
    saveTweet(tweet){},
    observeTweet(){},
    replyFilter(content){},
    renderTweet($div){},
    createTweetDiv(tweet){},
    username(){},
    onLogoutClick(){},
    onClickNotifier(){},
    observeNotifier(user){}
  });

  global.FEED = FEED;

  return global;

}(window));

FEEDクラスをwindowオブジェクトに逃がしてあげて、main.jsxで呼び出す形の実装になっています。

ログイン如何に合わせてランディングページを表示するか、フィードを表示するかの切り分けは以下のようにすると良いでしょう。

今回は、フィードコンポーネントにデータストアから取得した値を渡してレンダリングする設計になっています(React上でMilkcocoaを使う際のもっと良い設計があれば随時報告します⁠⁠。

React.render関数によって組み立てられたコンポーネントがMount(DOMに繋ぎ込まれる)のですが、第三引数のcallback関数はDOMが読み込まれたあとの処理と同義になります。今回はjsxファイルをHTMLに読み込む際にharmonyオプションを使用しているので、thisのスコープがES6のようになっているのでより素直に使用できます。

main.jsx
(function(){
  milkcocoa.getCurrentUser( (err, user) => {
    var isLoggedIn = (err == null);
    if(isLoggedIn){
      milkcocoa.dataStore("tweet").query({}).done(tweets=>{
        
milkcocoa.dataStore(`notifications/${user.password.email.split("@")[0]}`).query({}).done(notifications=>{

          React.render(<FEED tweets={tweets} user={user} 
notifications={notifications} />, document.body,function(){
            this.observeTweet();
            this.observeNotifier(user.password.email.split("@")[0]);
          });
        });
      });
    } else {
      React.render(<LP />, document.body);
    }
  });
}());

ログイン後画面の根幹になるFEEDクラスのrender関数は以下のような実装になっています。

  • 通知数の表示ボックス
  • display:noneされているリプライ一覧
  • tweet一覧
  • ログアウトボタン

が描画されています。

一覧表示をする際に配列をmapしているあたりが少しややこしいかもしれませんね。

feed_component.jsx
render(){
  var tweets = this.props.tweets;
  return (<div id="feed">
    <div id="notifier" 
onClick={this.onClickNotifier}>{this.props.notifications.length}</div>

    <NOTIFICATIONS notifications={this.props.notifications} 
username={this.username()} />

    <h1>{this.username() + "'s feed"}</h1>
    <input type="text" placeholder="Tweets here!" 
onKeyPress={this.onInputKeyPress} />
    <div id="tweet_list">
      {tweets.map((tweet)=>{
        return <div key={tweet.id} className="tweet">
          <p>{tweet.user}</p>
          <p>{tweet.content}</p>
        </div>;
      })}
    </div>
    <button onClick={this.onLogoutClick}>Logout</button>
  </div>);
}

通知ビューの実装

NOTIFICATIONコンポーネントは非常にシンプルです。

役割は

  • 描画
  • 通知監視
  • イベントリスナー

だけです。

notification_component.jsx
(function(global){
  var NOTIFICATIONS = React.createClass({
    render(){},
    observeNotification(){},
    onNotificationClick(e){}
  });

  global.NOTIFICATIONS = NOTIFICATIONS;
  return global;
}(window));

同期の実装

通知監視だけBaaS特有の部分であり、React.jsとの絡みの独特さがあるので、実装を解説しましょう。

notification_component.jsx
observeNotification(){
  var ds_notification = 
milkcocoa.dataStore(`notifications/${this.props.username}`);

  ds_notification.on("set", data=>{
    var $li = document.createElement("li");
    $li.innerHTML = data.value.content;
    $li.setAttribute("id", data.id);
    $li.addEventListener("click", this.onNotificationClick);
    document.getElementById("notification_list").appendChild($li);
  });
  ds_notification.on("remove", data=>{
    document.getElementById(data.id).remove();
  });
}

要点だけ見ましょう。

ds_notification.on("set", data=>{
  // li要素作成
});
ds_notification.on("remove", data=>{
  // li要素削除
});

はい、とてもシンプルですね。tweetと同時に通知データストアに通知がsetされますので、それを検知したらリアルタイムに通知を増加させます。同様に、通知をクリックしたら既読ということで通知データストアから通知を削除しますので、そのデータストアイベントを監視して要素を削除している訳です。

同様に、通知に関わる実装がもう一箇所あるので見てみましょう。

feed_component.jsx
observeNotifier(user){
  var ds_notification = milkcocoa.dataStore(`notifications/${user}`);
  var len = this.props.notifications.length;
  var $notifier = document.getElementById("notifier");
  ds_notification.on("set", data=>{
    len++;
    $notifier.innerHTML = len;

  });
  ds_notification.on("remove", data=>{
    len--;
    $notifier.innerHTML = len;
  });
}

こちらも、通知ボックスの通知残数の変更を監視するものですね。とてもシンプルなのでもはや説明も不要なくらいです。

ツイートフィルタの実装

tweetの中に自分の名前があったら通知するということで、フィルタ関数を実装したら終わりです(ちょっと雑なのでTwitterほど良い感じに@を取得するわけではありませんが⁠⁠。

feed_component.jsx
replyFilter(content){
  var called_name = "";
  var base = content.split("@");
  for(var i=base.length-1; i>=1;i--){
    called_name = base[i].split(" ")[0];
  }
  return called_name;
}

データストアの設計

今回のデータストアの設計を解説すると、

  • tweetデータストア
  • notification/<user_id>データストア

の2つになります。

tweetデータストアは全体のツイートが集まるものになります。notification/<user_id>データストアは、ログインしているユーザが自分のデータストアを監視して、通知を受け取るためにあります。

このように、データストアを巨大な連想配列のように自由に使い捨てるスタイルで設計するので、RDBと違って初めは戸惑うかもしれませんが、柔軟で高速な開発が実現できて大変良いです。

全体の動作確認

以上のソースコードをgulpでローカルサーバに置いてlocalhost:8000にアクセスすると以下のような感じになります。

図1 スクリーンショット
図1 スクリーンショット

是非一度アクセスして、自分の名前にリプライを飛ばしてみてください。なかなか今までのWebアプリケーションフレームワークで苦労していたことを、BaaSでちょちょいと実装できていて壮快ですよ。

まとめ

BaaSを用いるとerb、Jade、PHPのようなテンプレートエンジン的なものを用いないうえに、HTTPリクエストのルーティングの必要がなくなってしまうために、全体の傾向としてSPA(Single Page Application)として実装するケースが多くなると思います。そんな中で今回の実装がひとつの指針になれば良いなと思います。

もちろん、意図的にページを分割し、1ページの中の動きある部分を最小限に保つことは可能です。その場合、既存のフレームワークと組み合わせてBaaSの同期機能などを部分的に利用する使い方もありますし、jQueryだけでプロトタイピング的に実装を完結させることもできます。様々な使い方を発想できるので、いろいろと試してみるのをお勧めします。

おすすめ記事

記事・ニュース一覧