
こんにちは 私の名前はValery Zaitsevです。Mail.ruTargetプロジェクトのクライアント側開発者です。 私たちのプロジェクトでは、悪名高いBackbone.jsライブラリを使用していますが、もちろん何かが欠けていました。 私たちの問題の可能な解決策を考えた後、私はBackbone.jsへのアドオンを書くことにしました。
Ribs.jsは、Backboneの機能を拡張するライブラリです。 そして、美しさは変化するのではなく、拡大することです。 前と同じようにお気に入りのバックボーンを使用できますが、必要に応じて新しい機能を使用します。
- ネストされた属性:ネストのモデル属性を操作します。
- 計算された属性:依存関係(モデルの他の属性)が変更されたときに自動的に再計算される属性をモデルに追加します。
- バインディング:モデル属性とDOM要素間の動的な関係。
これらの機能をより詳細に検討してください。
属性
最も単純で最も明白なものから始めましょう。 Backboneで多くのことを書いている場合は、属性がフラットではないモデルに変更を加える必要があるときに問題が発生する可能性があります。var Simpsons = Backbone.Ribs.Model.extend({ defaults: { homer: { age: 40, weight: 90, job: 'Safety Inspector' }, bart: { age: 10, weight: 30, job: '4th grade student' } } }); var family = new Simpsons();
ホーマーがしっかりと食事を取り、数ポンド体重を増やしたとします。
バックボーン:
var homer = _.clone(family.get('homer')); homer.weight = 92; family.set('homer', homer);
get / setアプローチに違反しないようにするには、次のものが必要です。
- モデルからオブジェクトを取得します。
- このオブジェクトのコピーを作成します。
- 必要な変更を加えます。
- 戻す。
同意します、これは非常に不便です。 また、オブジェクトが巨大になる可能性があるという事実を考えると、非常に高価です。 必要な属性を正確に変更するのがはるかに簡単です:
バックボーン+リブ:
family.set('homer.weight', 92);
このセットaの結果として、イベント
'change:homer.weight'
が生成されます。 ネストチェーン全体に沿ってイベントを生成する必要がある可能性があります。 これを行うには、
{propagation: true}
をsetメソッドに渡します。
family.set('homer.weight', 92, {propagation: true});
この場合、イベント
'change:homer.weight'
および
'change:homer'
が生成されます。
計算された属性
私はそれらを計算フィールドと呼ぶことに慣れているとすぐに言わなければならないので、二重の用語をおforびします。 それでは始めましょう。 モデルの属性を特定の形式に変換する必要がある場合(「結果」と呼びましょう)に、1つの場所ではなく、この結果を使用する状況が非常に頻繁に発生します。 そして、属性を変更するときに結果が更新され、それに関連するすべてのものも更新されると便利です。 その結果、かなり面倒な一連の追加メソッドとサブスクリプションが作成されますが、将来的には維持するのが非常に困難になります。たとえば、フリンク教授は、ホーマーとバートの総重量を制御することが非常に重要である、ある種のクレイジーな研究を思いつきました。 純粋なBackboneとBackbone + Ribsの実装を比較してみましょう。
バックボーン:
var Simpsons = Backbone.Model.extend({ defaults: { homer: { age: 40, weight: 90, job: 'Safety Inspector' }, bart: { age: 10, weight: 30, job: '4th grade student' } } }); var family = new Simpsons(), doSmth = function (model, value) { console.log(value); }; family.on('change:bart', function (model, bart) { var prev = family.previous('bart').weight; if (bart.weight !== prev) { doSmth(family, bart.weight + family.get('homer').weight); } }); family.on('change:homer', function (model, homer) { var prev = family.previous('homer').weight; if (homer.weight !== prev) { doSmth(family, homer.weight + family.get('bart').weight); } }); var bart = _.clone(family.get('bart')); bart.weight = 32; family.set('bart', bart);// : 122 var homer = _.clone(family.get('homer')); homer.weight = 91; family.set('homer', homer);// : 123
少し違った方法で書くこともできますが、これでは状況はあまり保存されません。 ここで書いたことを理解しましょう。 希望する総重量で何かをする関数を決定しました。
'change:homer'
および
'change:bart'
の処理をサブスク
'change:bart'
。 ハンドラーで、重み値が変更されたかどうかを確認し、この場合は作業関数を呼び出します。 同意します。かなり単純で一般的な状況には多くの落書きがあります。 今では同じことですが、短く、明確で、簡単です。
バックボーン+リブ:
var Simpsons = Backbone.Ribs.Model.extend({ defaults: { homer: { age: 40, weight: 90, job: 'Safety Inspector' }, bart: { age: 10, weight: 30, job: '4th grade student' } }, computeds: { totalWeight: { deps: ['homer.weight', 'bart.weight'], get: function (h, b) { return h + b; } } } }); var family = new Simpsons(), doSmth = function (model, value) { console.log(value); }; family.on('change:totalWeight', doSmth); family.set('bart.weight', 32); // : 122 family.set('homer.weight', 91); // : 123
ここで何が起こっているのですか?! 2つの属性に依存する計算フィールドを追加しました。 属性を変更すると、計算フィールドは自動的に再計算されます。 計算された属性は、通常の属性と考えることができます。
その意味を読むことができます:
family.get('totalWeight'); // 120
変更を購読できます:
family.on('change:totalWeight', function () {});
必要に応じて、計算フィールドの設定方法を説明し、良心のtwinめなしに設定できます。 計算フィールドは、他の計算フィールドの依存関係で使用できることに注意してください。 また、計算フィールドはバインダーで非常に便利です!
バインディング
バインディングは、モデルとDOM要素の間の関係です。 ここでもっと簡単に言うことはできません。 Web開発者は毎日、あらゆる種類のデータをインターフェイスに出力する必要があります。 変更を追跡します。 更新。 再び撤回...そして、営業日が終わりました。 黄色い友達に戻りましょう。 あるspan
総重量を表示したいとしましょう。
バックボーン:
var Simpsons = Backbone.Model.extend({ defaults: { homer: { age: 40, weight: 90, job: 'Safety Inspector' }, bart: { age: 10, weight: 30, job: '4th grade student' } } }); var Table = Backbone.View.extend({ initialize: function (family) { this.family = family; family.on('change:bart', function (model, bart) { var prev = this.family.previous('bart').weight; if (bart.weight !== prev) { this.onchangeTotalWeight(bart.weight + family.get('homer').weight); } }, this); family.on('change:homer', function (model, homer) { var prev = family.previous('homer').weight; if (homer.weight !== prev) { this.onchangeTotalWeight(homer.weight + family.get('bart').weight); } }, this); }, onchangeTotalWeight: function (totalWeight) { this.$('span').text(totalWeight); } }); var family = new Simpsons(), table = new Table(family);
バックボーン+リブ:
var Simpsons = Backbone.Ribs.Model.extend({ defaults: { homer: { age: 40, weight: 90, job: 'Safety Inspector' }, bart: { age: 10, weight: 30, job: '4th grade student' } }, computeds: { totalWeight: { deps: ['homer.weight', 'bart.weight'], get: function (h, b) { return h + b; } } } }); var Table = Backbone.Ribs.View.extend({ bindings: { 'span': {text: 'family.totalWeight'} }, initialize: function (family) { this.family = family; } }); var family = new Simpsons(), table = new Table(family);
これで、ホーマーまたはバートの重量が変更されると、
span
が更新されます。 テキストに加えて、DOM要素のパラメーターとモデル属性との間に他の関係を作成できます。
- さまざまなタイプの入力(テキスト、チェックボックス、ラジオ)との双方向通信。
- css属性。
- cssクラス。
- 修飾子
- そしてもう一つ。
Ribs.jsの通常のバインダーに加えて、 コレクションバインダーを作成できます。 このメカニズムの説明は別の記事に値するので、この記事では簡単に説明します。 コレクションのバインディングは、モデルのコレクション、Backbone.View、および特定のDOM要素をリンクします。 コレクションのモデルごとに、Viewの独自のインスタンスが作成され、DOM要素に配置されます。 さらに、コレクションの変更(モデルの追加/削除、並べ替え)により、ユーザーの介入なしにインターフェイスが更新されます。 したがって、コレクション全体の動的ビューを取得できます。 範囲は明らかです-同じデータを持つさまざまなリストと構造。
なぜRibs.jsであり、他の何かではないのですか?
インターネットには、ネストされた属性を操作する機能を追加する多くのライブラリがあります。 バインダーを実装するライブラリがあります。 しかし、これらは異なるライブラリであり、それらを一緒に動作させることは非常に難しい作業ですが、ほとんどの場合不可能です。Ribs.jsの3つのコンポーネント(ネストされた属性、計算フィールド、バインダー)は、互いに独立して動作できます。 しかし、それらを一緒に使用すると、すべてのパワーが明らかになります(最後の例はこれを示しています)。
私の最も近い競争相手はEpoxy.jsです。 これは同様の機能を備えたライブラリですが、次のとおりです。
- 彼女は、ネストされた属性の操作方法を知りません。これは、すでに見たように、非常に便利なことです。
- 1つのコレクションは1つのバインディングでのみ使用できます(Ribs.jsでは、1つのコレクションに基づいてさまざまな種類の表現を作成できます)。
- 10,000個のEpoxy.jsモデルのコレクションをバインドしたテストでは、Ribs.jsはほぼ2倍劣っています。 テストのソースはこちらです。
- 実装と使いやすさのためのいくつかの難問があります。 困難なタスクでは、このため、回避策を考案し、松葉杖を挿入する必要があります。
Ribs.jsを使用すると、単純なメカニズムの実装に気を取られることなく、ビジネスロジックに集中できます。 コードはより見やすくコンパクトになり、これは開発自体とその後のサポートの両方に最も良い影響を及ぼします。 さらに、Ribs.jsの作業は継続されます。 Ribs.jsで実装された多くのアイデアは、実際の戦闘ミッションの作業の過程で生まれました。 これらのアイデアは出てくるでしょう、そして、最高のものはライブラリの将来のバージョンに分類されます。