DockYardでは、Webアプリケーションの構築からアドオンの作成と保守、Emberエコシステムへの貢献まで、Emberに多くの時間を費やしています。 Emberのベストプラクティス、パターン、アンチパターン、一般的な間違いに焦点を当てた一連の投稿を通じて得た経験の一部を共有したいと考えています。 これはこのシリーズの最初の投稿なので、 Ember.Object
の基本に戻ってEmber.Object
ましょう。
Ember.Object
は、Ember開発者として最初に学ぶものの1つであり、 Ember.Object
です。 Emberで作業するほぼすべてのオブジェクトは、ルート(Route)、コンポーネント(Component)、モデル(Model)、またはサービス(Service)であるかどうかにかかわらず、Ember.Objectから継承されます 。 しかし、時々、私はそれが間違って使用される方法を見ます:
export default Ember.Component.extend({ items: [], actions: { addItem(item) { this.get('items').pushObject(item); } } });
これに遭遇したことがある人にとっては、問題は明らかです。
Ember.Object
APIを見て、すべてのInherited 、 Protected 、およびPrivateオプションの選択を解除すると、 Ember.Object
は独自のメソッドとプロパティがないことがわかります。 ソースコードを短くすることはできません。 これはObservable
と混合されたEmber CoreObject
リテラル拡張です:
var EmberObject = CoreObject.extend(Observable);
CoreObject
は、ファクトリまたはクラスを定義するためのクリーンなインターフェイスを提供します。 これは基本的に、通常コンストラクタ関数を作成し、メソッドとプロパティをプロトタイプで定義してから、 new SomeConstructor()
呼び出してnew SomeConstructor()
オブジェクトを作成する方法に関する抽象化です。 this._super()
を使用してスーパークラスメソッドを呼び出したり、 不純物を介して一連のプロパティをクラスに結合したりするには、 CoreObject
感謝する必要があります。 inmber、 create
、 extend
、またはreopen
など、Emberオブジェクトで頻繁に使用する必要のあるすべてのメソッドがそこで定義されています。
Observable
は混合(Mixin)です。これにより、オブジェクトのプロパティの変化、およびget
およびset
の呼び出し時の変化を観察できます。
Emberアプリケーションを開発する場合、 CoreObject
を使用する必要はありません。 代わりに、 Ember.Object
を継承しEmber.Object
。 結局、Emberには変更に対する最も重要な応答があるため、プロパティ値の変更を検出するにはObservable
メソッドが必要です。
新しいクラスのお知らせ
Ember.Object
を拡張することにより、新しいタイプのオブザーバブルオブジェクトを定義できます。
const Post = Ember.Object.extend({ title: 'Untitled', author: 'Anonymous', header: computed('title', 'author', function() { const title = this.get('title'); const author = this.get('author'); return `"${title}" by ${author}`; }) });
Post.create()
呼び出すことで、 Post
型の新しいオブジェクトを作成できるようになりました。 各レコードについて、 Post
クラスで宣言されたプロパティとメソッドが継承されます。
const post = Post.create(); post.get('title'); // => 'Untitled' post.get('author'); // => 'Anonymous' post.get('header'); // => 'Untitled by Anonymous' post instanceof Post; // => true
プロパティの値を変更し、投稿に作成者の名前と名前を付けることができます。 これらの値はクラスではなくインスタンスで設定されるため、作成される投稿には影響しません。
post.set('title', 'Heads? Or Tails?'); post.set('author', 'R & R Lutece'); post.get('header'); // => '"Heads? Or Tails?" by R & R Lutece' const anotherPost = Post.create(); anotherPost.get('title'); // => 'Untitled' anotherPost.get('author'); // => 'Anonymous' anotherPost.get('header'); // => 'Untitled by Anonymous'
この方法でプロパティを更新しても他のインスタンスには影響しないため、この例で実行されるすべての操作は安全であると考えるのは簡単です。 しかし、これについてもう少し詳しく見ていきましょう。
クラス内の状態リーク
投稿にはタグの追加リストを含めることができるため、 tags
と呼ばれるプロパティを作成できます。デフォルトでは空の配列です。 addTag()
メソッドを呼び出すと、新しいタグを追加できます。
const Post = Ember.Object.extend({ tags: [], addTag(tag) { this.get('tags').pushObject(tag); } }); const post = Post.create(); post.get('tags'); // => [] post.addTag('constants'); post.addTag('variables'); post.get('tags'); // => ['constants', 'variables']
動作しているようです! しかし、2番目の投稿を作成した後に何が起こるかを確認しましょう。
const anotherPost = Post.create(); anotherPost.get('tags'); // => ['constants', 'variables']
目標が空のタグを持つ新しい投稿を作成することであった場合(デフォルトで想定)、投稿は以前の投稿のタグで作成されました。 tags
プロパティの新しい値が設定されていないため、メイン配列が変更されただけです。 そのため、状態を事実上Post
クラスにスローし、すべてのインスタンスで使用します。
post.get('tags'); // => ['constants', 'variables'] anotherPost.get('tags'); // => ['constants', 'variables'] anotherPost.addTag('infinity'); // => ['constants', 'variables', 'infinity'] post.get('tags'); // => ['constants', 'variables', 'infinity']
これは、インスタンスの状態とクラスの状態を混同できる唯一のシナリオではありませんが、もちろん、より一般的なシナリオです。 次の例では、 new Date()
を渡すことで、現在の日時のcreatedDate
のデフォルト値を設定できます。 ただし、クラスが定義されると、 new Date()
1回評価されます。 したがって、このクラスの新しいインスタンスをいつ作成するかに関係なく、それらはすべて同じcreatedDate
値をcreatedDate
ます。
const Post = Ember.Object.extend({ createdDate: new Date() }); const postA = Post.create(); postA.get('createdDate'); // => Fri Sep 18 2015 13:47:02 GMT-0400 (EDT) // Sometime in the future... const postB = Post.create(); postB.get('createdDate'); // => Fri Sep 18 2015 13:47:02 GMT-0400 (EDT)
状況を管理する方法は?
投稿間でタグを共有しないようにするには、オブジェクトの初期化中にtags
プロパティを設定する必要があります。
const Post = Ember.Object.extend({ init() { this._super(...arguments); this.tags = []; } });
init
はPost.create()
呼び出されるたびにPost.create()
れるため、各インスタンスポストは常に独自のtags
配列を取得しtags
。 さらに、 tags
計算プロパティにすることができます。
const Post = Ember.Object.extend({ tags: computed({ return []; }) });
おわりに
この投稿の冒頭の例のように、このようなコンポーネントを記述してはならない理由は明らかです。 ルートを終了するときにコンポーネントがページに1回だけ表示されても、ファクトリではなくコンポーネントインスタンスのみが破棄されます。 そのため、戻ったとき、コンポーネントの新しいインスタンスには前のページ訪問のトレースがあります。
このエラーは、不純物の使用時に発生する可能性があります。 Ember.Mixin
はEmber.Mixin
はないという事実にもかかわらず、その中で宣言されているプロパティとメソッドは、 Ember.Object
と混ざり合っていEmber.Object
。 結果は同じになります。最終的に、混合物を使用するすべてのオブジェクト間で状態を分割できます。