【古典】 jQueryでSPAする時のポイント。もしくはオブザーバーパターンのサンプル。

TECHDRIVE の佐藤です。今回は書いてみたコードについて解説しつつ進めたいと思っています。

はじめに

歴史は繰り返しているので「またそういう話か」ということはよくあるのですが、いかに流行っている(流行っていそうな)ライブラリやフレームワークでも、多くの開発現場にいきなり導入できる事は少なく、古い技術を使ってやりくりしている事は多くあります。

特にJavaScriptでSPAなどのところは「どうしても必須でこれが無いと成り立たない」というレベルにできる業務領域がまだそれ程大きく無い為か、私や私の知り合いが対応している開発の現場では未だにjQueryでDOM操作がかなり多いと思われます。

そういう中でも「この部分だけ、ここだけで良いからSPAぽくしたい」というような要望はたまにあります。そういう時にいきなりVue.jsReactが導入できるような現場も稀有なのでは無いでしょうか(その背景についてはここでは論じません)。

というわけで今回はjQueryでSPAしたサンプルの解説記事です。jQuery使ってもごちゃごちゃにならない考え方もそこそこ入っていると思います。

作ったもの

https://github.com/CircleAround/message_server_js ここにあります。動作サンプルは以下です。 https://message-server-js-app.herokuapp.com/

内容としては https://message-server-app.herokuapp.com/ に置いてあるごくごく簡単なAPIサーバー(ソースはこちら)の、JavaScriptでのサンプルです。

今回の実装では「jQuery&レガシーバニラJS」縛りでお送りしています(forEachとかfind使ったのは許して下さい)。

ポイント

オブザーバーパターンを利用してViewとロジックを分ける

古式ゆかしいクライアント実装の設計ではMVCがよく用いられていると思いますが、大事なのはMとVの間がオブザーバーパターンで書かれていることだと思います。

「ViewはModelの詳細について知っているが、ModelはViewについて無知であ る」

ということをキッチリすると、どれが大事なロジックであり、どれが描画の為のコードなのかが明瞭になります。

オブザーバーパターンは 「データの状態の変化を自ら通知せずに、勝手に取得させる」 という考え方なので、それに忠実に書きます。 クラス名のプレフィクスはModelにする方が良いかもしれませんが、趣味でMessageServiceとしています*1

https://github.com/CircleAround/message_server_js/blob/master/main.js#L89

MessageService はクライアントで保持しなければいけない以下のような情報を持っています。

  • ログインしたユーザー
  • 表示しなければならない全てのメッセージ

また、情報の変化を受け取るリスナを仕掛けられます。リスナの実装は複数考え方があるとは思いますが、今回はシンプルに「変数に関数を設定できるだけ」にしています。以下のあたりです。

https://github.com/CircleAround/message_server_js/blob/master/main.js#L344

    this.service.onChangeMessages = function() {
      _this.updateMessages(_this.service.messages); // メッセージ一覧に変更がなされたメッセージの見た目を更新する
    };

    this.service.onChangeTargetMessage = function() {
      _this.updateForm(); // 編集のためのターゲットメッセージが変更されたら、フォームを更新する
    };

保有している状態が通信の結果などで変化した際に、このリスナを実行するようにしています。例えば signup関数が呼ばれると、通信が成功すれば onSignUp が呼ばれるようにしています。

  MessageService.prototype.signup = function (email) {
    var _this = this;
    return this.handleError(this.connector.signup(email).then(function(data){
      _this.user = data;
      _this.onSignUp();
    }));
  }

長い文字列はHTMLから取得して編集しやすくする

https://github.com/CircleAround/message_server_js/blob/master/index.html#L48

このあたりです。<script></script> に typeをうまく指定すると中身が文字列であるような扱いをしてくれるので、これを使ってHTML内に文字列を埋め込みます。今回はメッセージ表示のDOM表示部です。

    <script id="message_template" type="text/template">
      <div class="message">
        <div class="user">user:<span class="id"></span></div>      
        <div class="contents"></div>
        <div class="actions">
          <a class="edit" href="javascript:void(0)">編集</a>
          <a class="delete" href="javascript:void(0)">削除</a>
        </div> 
      </div>
    </script>

https://github.com/CircleAround/message_server_js/blob/master/main.js#L194

使う時にはIDでセレクタを辿ってから jQuery.text() を使っています。

文字列連結を避けてXSSを回避する

最近社内でも話題になった事ですが 「HTMLを文字列連結で作成してjQuery.html()でDOM化すると、XSSの危険性が増す」 ということがあります。文字列連結を行うと埋め込まれた変数が本当に安全かを由来を辿って確認する必要が出ててきてしまい、抜け漏れが発生しやすくなるということですね。

そこで私がよく用いる方法は 「jQuery.appendTo()を使ってDOM化して、その戻り値オブジェクトを介してjQuery.text()を利用する」 です。

https://github.com/CircleAround/message_server_js/blob/master/main.js#L220

      var $message = $(_this.messageTemplate).appendTo($messages);
      $message.find('.contents').text(message.contents);
      $message.find('.user>.id').text(message.user_id);

こんな形で、jQuery.appendTo()の戻り値が「今作成されたDOM構造」へのオブジェクトを戻してくれるので、そこからfind()で辿ってtext()で値を入れています。text()なら確実にエスケープされるのでこの効果を利用しています。

クラス的なオブジェクトを作成する時は無名関数で囲むとプライベートな関数が置けて便利

https://github.com/CircleAround/message_server_js/blob/master/main.js#L5

JavaScriptは都合よくスコープを作れると適切に書けそうな気がしています。クラス的なオブジェクトを作る場合にも、下記のようにスコープの中身が外に漏れ出ないことを利用しています。

var Connector = function() {
  
  // プライベートな関数群。これは公開されない。
  function request(params) {
    params['dataType'] = 'json';
    return $.ajax(params);
  }

  function url(path) {
    return endpoint + path;
  }

  function Connector(){} // ここからクラスの内容

  ...

  return Connector; // 最後にコンストラクタを返せばクラスだけ公開できる
}();

おしまいに

まぁ、滅びゆく技術ですよね。ただ、そういうものが必要な開発現場もあるので適切に利用できれば良いかと。

最近だと「フレームワークを使わないとうまく作れない」というような風潮を感じますが、便利なものが利用できない時でも上手に設計することでうまくやれるのではないかなと思いますし、そういう姿を目指していたいです。ReactをViewの部分に入れる変更をしてみるのもいいかなと思いました。差し替え可能感が感じられそうなのと、状態管理フレームワーク無しのReactはこういうところに価値がありそうに思うのです。

このコードはあえて縛っていますが、業務でやるなら外部ライブラリを少し入れます。これだと1ファイルが長過ぎますしね。そういうのも今後書くかもしれません。下記のようなことと組み合わせていく感じです。

dev.techdrive.top

*1:イメージ的にApplicationServiceに近い部分を担うため

TECH DRIVEについて

TECH DRIVEは「技術者の成長を加速させる」をキーワードに都内で活動をしているコミュニティです。
TwitterやFacebookにて技術ネタやイベント情報の発信を行っていますので、ご興味があれば、いいねやフォローをお願いいたします。