LoginSignup
3
3

More than 5 years have passed since last update.

T3 JavaScript Framework試した

Posted at

T3 JavaScript Framework

DropBox, SkyDrive, Google Driveと競合するクラウド・ストレージサービスのBoxがフロントエンドに使っているJavaScriptフレームワーク。

他のフレームワークと違いフレームワークとして提供される機能は薄いが、テストしやすい疎結合な設計を目指している。その目標としては、バッドパーツを作りにくいようにすることを挙げている。

また、他のフレームワークのようにMVC, MVP, MVVMといった分類での構成を持たないので、Backbone, Reactのような他のフレームワークやライブラリとも協調させられるとしている。

T3のコンポーネント

T3ではコンポーネントとして以下の3種類を定義している。

  • Modules 特定のDOMと結びつけられ、表示やイベントの制御を行う
  • Services DOMとは切り離された再利用可能なツール (Ajax, Cookie, etc.)
  • Behaviors Modulesをまたいで再利用されるイベント制御のためのmixin

疎結合な状態を保つため、Modules, Behaviorsはお互いに直接操作することを禁じられており、メッセージングにより操作を行う。

インストール、起動

Browserifyでのビルドは今のところ対応していないので、NPMインストール、ダウンロード、rawgit経由のいずれかでJSファイルをscriptタグから読み込む。イベント制御に別途jQueryが必要。

$ npm install jquery t3js
<script src="./node_modules/jquery/dist/jquery.js"></script>
<script src="./node_modules/t3js/dist/t3.js"></script>

T3コンポーネントの管理はグローバルに定義されているBox.Applicationが行う。コンポーネントを管理された状態におくには、コンポーネント定義後にBox.Application.init()を呼び出す。呼び出すタイミングはHTML最下段かDOMContentLoadedonloadあたり。

ちなみにBox.Applicationの存在はBoxのログイン画面でも見ることができる。開発者ツールを立ち上げてBox.Applicationを叩くとオブジェクトが存在していることがわかる。

Module

<div data-module="module-name">
  <button data-type="push-me-btn">push me</button>
  pushed <span class="pushed-times"></span> time(s).
</div>
Box.Application.addModule('module-name', function(context) {
  'use strict';

  var counterEl, counter;

  return {
    init: function() {
      var moduleEl = context.getElement();
      counterEl = moduleEl.querySelector('.pushed-times');
      counter = 0;
      counterEl.innerHTML = counter;
    },

    onclick: function(event, element, elementType) {
      if (elementType == 'push-me-btn') {
        counter++;
        counterEl.innerHTML = counter;
      }
    },

    destroy: function() {
      counterEl = null;
      counter = null;
    }
  };
});

DOMにdata-moduleを定義すると自動的に該当のModuleへバインドされる。バインディングは用意されていないので、他にバインディングを用意しない場合はHTMLを書いてJavaScriptでDOM操作することで変更を反映していくこととなる。

コンストラクタ、デストラクタがinit, destroyとして定義できるので、DOMオブジェクトのキャッシングやModuleが生きている間に参照される変数の初期化はこの時に行う。定義時に対象のDOMをラップしたcontextが与えられるので、DOMを取り出して扱うことができる。

特筆すべきは、data-moduleをつけたDOMの領域全てのクリックイベントがaddModuleで定義したonclickにdelegateされることである。onclick内部で処理を振り分けるためにも、data-type要素が必要とされる。これは他のDOMイベントにおいても同じことが言える。

Service

Box.Application.addService('count-service', function(application) {
  'use strict';

  var counter = 0;

  return {
    countUp: function() {
      counter++;
      return counter;
    }
  };
});

ServiceもModuleと同じようにaddService()で定義できるが、DOMに依存しない処理であるからBox.Applicationが引数として与えられる。Box.ApplicationからgetService()で他のServiceは取得できるが、先ほど挙げた原則どおり、Moduleへと直接アクセスすることはできない。

ModuleからServiceを利用する時は、contextからgetService()を使って名前で呼び出すことができる。

Module間メッセージング

前述のとおりModule同士が直接操作することは禁止されている。他のModuleに情報を伝達するには、context.broadcast(key, value)を使う。broadcastはその名のとおり、すべてのアクティブなModuleへと情報が伝達される。そのため、フィルタリングや処理の振り分けはbroadcastを受信するModule側で実装する。

Box.Application.addModule('module-name', function(context) {
  'use strict';

  return {
    messages: ['message-from-other-module'],

    onmessage: function(key, value) {
      if (key == 'message-from-other-module') {
        // 受信側Moduleでやりたい処理を記述する
      }
    }
  };
});

所感

Behaviorやテストについては今回省略したが、

  • 機能を薄く保つことで、特定の仕様と機能セットにロックインされることを防ぐ
  • コンポーネント間の相互作用をbroadcastメッセージングによって単純化、疎結合化する
  • その結果としてコンポーネント単体とメッセージングにのみ注目すればよくなる
  • (擬似的なものを含め)グローバル変数への依存を極力排除する

あたりはBoxにおけるJavaScript開発の実践知として裏打ちされているものと思われる。

一方でBox.Applicationというネームスペースをグローバルに掘っているところやCommonJS対応していないところに、わずかながら古さを感じる。

なおT3の設計思想として「Progressive Enhancementの観点から、サイトを便利にするためのJavaScriptは必須ではない」と言われているので、SPAとしての使用はいろいろ載せないと厳しそう。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3