Backbone.js Tips

Kazuhito Hokamura

2012.12.04

社内勉強会

今日の内容

Backbone.jsのTips的なのをいくつか紹介

  • 遅延評価されるプロパティ
  • Model内部で扱う型の変換
  • サーバーサイドとの共通化

遅延評価されるプロパティ

遅延評価されるプロパティ

値に関数を指定することで実行時に評価されるプロパティ。

  • defaults (Model)
  • urlRoot (Model)
  • url (Model, Collection)
  • events (View)
  • attributes (View)

(「getValue」でBackbone.jsのコードをgrepすればわかる)

遅延評価されるプロパティの例

createdのデフォルト値にインスタンスを作成した時刻を設定したい

var Todo = Backbone.Model.extend({
  defaults: {
    text: null,
    completed: false,
    created: new Date() // これだとクラスを作った時刻になってしまう
  }
});

遅延評価されるプロパティの例

関数にすることでインスタンス作成時に評価される

var Todo = Backbone.Model.extend({
  defaults: function() {
    return {
      text: null,
      completed: false,
      created: new Date()
    };
  }
});

遅延評価されるプロパティの例

Comments CollectionがURLで親のentry idを参照したい

var Comments = Backbone.Collection.extend({
  initialize: function(entry) {
    this.entry = entry;
  },
  // これだと定義時にthis.entryはないのでエラーになる
  url: '/entries/' + this.entry.get('id') + '/comments';
});

遅延評価されるプロパティの例

Comments CollectionがURLで親のentry idを参照したい

var Comments = Backbone.Collection.extend({
  initialize: function(entry) {
    this.entry = entry;
  },
  // 関数にすることで実行時に評価される
  url: function() {
    return '/entries/' + this.entry.get('id') + '/comments';
  }
});

Model内部で扱う型の変換

日付の変換の例

  • APIからは日付のデータは文字列で渡ってくる
  • JavaScriptでは文字列でなくDate型(もしくはMomentとか)で扱いたい
  • APIにPOSTするときは文字列に変換したい

サーバー側からのデータを変換

parseプロパティを使う

var MyModel = Backbone.Model.extend({
  parse: function(response) {
    response.date = new Date(response.date);

    return response;
  }
});

parseの問題点

parseプロパティは基本的にSync系のメソッドでしか実行されない

var model = new MyModel();

$form.submit(function() {
  // こういう場合にparseプロパティは無力・・
  model.set('date', $inputDate.val());
});

setを上書き

setを上書きするしかないけど引数チェックとかがけっこうめんどい

var MyModel = Backbone.Model.extend({
  set: function(key, val, options) {
    // Backbone.jsのsetのソースからもってきた引数チェック
    var attrs, attr, val;

    // Handle both `"key", value` and `{key: value}` -style arguments.
    if (_.isObject(key) || key == null) {
      attrs = key;
      options = value;
    } else {
      attrs = {};
      attrs[key] = value;
    }

    if (!attrs) return this;
    if (attrs instanceof Model) attrs = attrs.attributes;

    // これがやりたいだけ
    attrs.date = new Date(attrs.date);

    // 親のsetを呼び出す
    return Backbone.Model.set.call(this, attrs, options);
  }
});

setを上書きしなくてもいいようなパッチ送ったらそんもんset上書きしてやれって一蹴されました\(^o^)/

サーバーにPOSTする場合

toJSONを上書きする

var MyModel = Backbone.Model.extend({
  toJSON: function() {
    // 元のtoJSONのコードはこれを返してるだけ
    var attr = _.clone(this.attributes);

    // きっとこういう便利関数がどこかに定義されてるはず
    attr.date = Utils.formatDate(attr.date, '%Y-%m-%dT%H:%m:%sZ');

    return attr;
  }
});

サーバーサイドとの共通化

サーバーサイドとの共通化

問題点

  • クライアントサイドとサーバーサイドでロジックが重複してしまうことがある
  • バリデーションなどはできるだけ共通化したい

サーバーサイドとの共通化

解決案

  • Backbone.jsはNode.jsでも使える(npmでインストール可能)
  • 両方で使えるモデルのベースを作れば共通化できそう

共通のベースクラスを作成

(function(global) {
  var Backbone = global.Backbone || require('backbone');

  var MyModelBase = Backbone.Model.extend({
    // 共通のロジック
    validate: function(attrs, opts) {
      if (attrs.text.length > 100) return new Error('text is too long');
    }
  });

  if (typeof window === 'undefined') {
    // for Node.js
    module.exports = MyModelBase;
  } else {
    // for browser
    global.MyApp.MyModelBase = MyModelBase;
  }
})(this);

Node.jsのコード

// Node.js
var MyModelBase = require('./MyModelBase');

var MyModelServer = MyModelBase.extend({
  // サーバー側で使うメソッドとか
});

module.exports = MyModelServer;

クライアントサイド

<script src="/path/to/myModelBase.js"></script>
MyApp.MyModelClient = MyApp.MyModelBase.extend({
  // クライアント側のコード
});

Thanks.