Ember.js開発者が犯す8つの最も一般的な間違い

公開: 2022-03-11

Ember.jsは、複雑なクライアント側アプリケーションを構築するための包括的なフレームワークです。 その信条の1つは「設定より規約」であり、ほとんどのWebアプリケーションに共通する開発の大部分が存在するという信念であり、したがって、これらの日常的な課題のほとんどを解決するための単一の最良の方法です。 ただし、適切な抽象化を見つけ、すべてのケースをカバーするには、時間とコミュニティ全体からの入力が必要です。 推論が進むにつれて、解決策を見つける必要があるときに私たちの手を投げて誰もが自分で身を守るのではなく、時間をかけてコア問題の解決策を正しく取得し、それをフレームワークに焼き付ける方が良いです。

Ember.jsは、開発をさらに容易にするために絶えず進化しています。 ただし、他の高度なフレームワークと同様に、Ember開発者が陥る可能性のある落とし穴がまだあります。 次の投稿で、これらを回避するためのマップを提供したいと思います。 さっそく飛び込もう!

よくある間違いその1:すべてのコンテキストオブジェクトが渡されたときにモデルフックが起動することを期待する

アプリケーションに次のルートがあると仮定します。

 Router.map(function() { this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });

bandルートには動的セグメントidがあります。 アプリに/bands/24のようなURLが読み込まれると、 24が対応するルートのmodelフックbandに渡されます。 モデルフックには、セグメントを逆シリアル化して、テンプレートで使用できるオブジェクト(またはオブジェクトの配列)を作成する役割があります。

 // app/routes/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); // params.id is '24' } });

ここまでは順調ですね。 ただし、ブラウザのナビゲーションバーからアプリケーションをロードする以外に、ルートを入力する方法は他にもあります。 それらの1つは、テンプレートlink-toを使用しています。 次のスニペットは、バンドのリストを調べて、それぞれのbandルートへのリンクを作成します。

 {{#each bands as |band|}} {{link-to band.name "band" band}} {{/each}}

link-toの最後の引数bandは、ルートの動的セグメントを埋めるオブジェクトであるため、そのidがルートのidセグメントになります。 多くの人が陥る罠は、モデルがすでにわかっていて渡されているため、その場合はモデルフックが呼び出されないことです。これは理にかなっており、サーバーへの要求を保存する可能性がありますが、確かにそうではありません。直感的。 それを回避する独創的な方法は、オブジェクト自体ではなく、そのIDを渡すことです。

 {{#each bands as |band|}} {{link-to band.name "band" band.id}} {{/each}} 

Ember.js

エンバーの緩和計画

ルーティング可能なコンポーネントは、おそらくバージョン2.1または2.2でまもなくEmberに登場する予定です。 それらが着陸すると、動的セグメントを持つルートにどのように移行しても、モデルフックは常に呼び出されます。 ここで対応するRFCを読んでください。

よくある間違いその2:ルート駆動型コントローラーがシングルトンであることを忘れる

Ember.jsのルートは、対応するテンプレートのコンテキストとして機能するコントローラーにプロパティを設定します。 これらのコントローラーはシングルトンであるため、コントローラーがアクティブでなくなった場合でも、コントローラーで定義された状態は存続します。

これは見落としがちなことで、私もこれに出くわしました。 私の場合、バンドと曲を含む音楽カタログアプリがありました。 songsコントローラーのsongCreationStartedフラグは、ユーザーが特定のバンドの曲の作成を開始したことを示しています。 問題は、ユーザーが別のバンドに切り替えた場合、 songCreationStartedの値が持続し、途中で完成した曲が他のバンドのものであるように見え、混乱していたことでした。

解決策は、残したくないコントローラーのプロパティを手動でリセットすることです。 これを行うための1つの可能な場所は、対応するルートのsetupControllerフックです。これは、 afterModelフック(その名前が示すように、 modelフックの後にあります)の後のすべての遷移で呼び出されます。

 // app/routes/band.js export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); controller.set('songCreationStarted', false); } });

エンバーの緩和計画

繰り返しになりますが、ルーティング可能なコンポーネントの夜明けはこの問題を解決し、コントローラーを完全に終わらせます。 ルーティング可能なコンポーネントの利点の1つは、ライフサイクルがより一貫しており、ルートから移行するときに常に分解されることです。 彼らが到着すると、上記の問題はなくなります。

よくある間違いその3: setupControllerでデフォルトの実装を呼び出さない

Emberのルートには、アプリケーション固有の動作を定義するためのライフサイクルフックがいくつかあります。 対応するテンプレートのデータをフェッチするために使用されるmodelと、テンプレートのコンテキストであるコントローラーをセットアップするためのsetupControllerについてはすでに説明しました。

この後者のsetupControllerには、コントローラーのmodelプロパティとしてmodelフックからモデルを割り当てるという適切なデフォルトがあります。

 // ember-routing/lib/system/route.js setupController(controller, context, transition) { if (controller && (context !== undefined)) { set(controller, 'model', context); } }

contextは、上記のmodelと呼んでいるember-routingパッケージで使用される名前です)

setupControllerフックは、コントローラーの状態をリセットするなど、いくつかの目的でオーバーライドできます(上記のよくある間違い2のように)。 ただし、上記でEmber.Routeにコピーした親実装を呼び出すのを忘れた場合、コントローラーにmodelプロパティが設定されていないため、長いヘッドスクラッチセッションに参加する可能性があります。 したがって、常にthis._super(controller, model)を呼び出します。

 export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); // put the custom setup here } });

エンバーの緩和計画

前に述べたように、コントローラーと、それらを使用するsetupControllerフックは間もなくなくなるため、この落とし穴はもはや脅威にはなりません。 ただし、ここで学ぶべきより大きな教訓があります。それは、祖先での実装に注意することです。 Emberのすべてのオブジェクトの母であるEmber.Objectで定義されているinit関数は、注意が必要なもう1つの例です。

よくある間違いその4:親以外のルートでthis.modelForを使用する

Emberルーターは、URLを処理するときに、各ルートセグメントのモデルを解決します。 アプリケーションに次のルートがあると仮定します。

 Router.map({ this.route('bands', function() { this.route('band', { path: ':id' }, function() { this.route('songs'); }); }); });

/bands/24/songsのURLを指定すると、 bandsbands.bandbands.band.songsの順にmodelフックが呼び出されます。 ルートAPIには、特に便利なメソッドmodelForがあります。このメソッドは、子ルートで使用して、親ルートの1つからモデルをフェッチできます。これは、そのモデルがその時点で確実に解決されているためです。

たとえば、次のコードは、 bands.bandルートでバンドオブジェクトをフェッチするための有効な方法です。

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); return bands.filterBy('id', params.id); } });

ただし、よくある間違いは、ルートの親ではないmodelForでルート名を使用することです。 上記の例のルートがわずかに変更された場合:

 Router.map({ this.route('bands'); this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });

bandsルートが親ではなくなり、そのモデルが解決されていないため、URLで指定されたバンドをフェッチする方法が機能しなくなります。

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); // `bands` is undefined return bands.filterBy('id', params.id); // => error! } });

解決策は、 modelForを親ルートにのみ使用し、ストアからのフェッチなど、 modelForを使用できない場合は、他の手段を使用して必要なデータを取得することです。

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); } });

よくある間違いその5:コンポーネントアクションが実行されるコンテキストの間違い

ネストされたコンポーネントは、常にEmberで推論するのが最も難しい部分の1つです。 Ember 1.10でのブロックパラメーターの導入により、この複雑さの多くは軽減されましたが、多くの状況では、子コンポーネントから起動されたアクションがトリガーされるコンポーネントを一目で確認するのは依然として困難です。

band-list-itemsを含むband-listコンポーネントがあり、各バンドをリスト内のお気に入りとしてマークできると仮定します。

 // app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band faveAction="setAsFavorite"}} {{/each}}

ユーザーがボタンをクリックしたときに呼び出されるアクション名は、 band-list-itemコンポーネントに渡され、そのfaveActionプロパティの値になります。

ここで、 band-list-itemのテンプレートとコンポーネントの定義を見てみましょう。

 // app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "faveBand"}}>Fave this</button>
 // app/components/band-list-item.js export default Ember.Component.extend({ band: null, faveAction: '', actions: { faveBand: { this.sendAction('faveAction', this.get('band')); } } });

ユーザーが[Favethis]ボタンをクリックすると、 faveBandアクションがトリガーされ、親コンポーネントであるband-listで渡されたコンポーネントのfaveAction (上記の場合はsetAsFavorite )が起動されます。

これは、ルート駆動型テンプレートからのアクションがコントローラー上で実行されるのと同じ方法でアクションが実行されることを期待しているため、多くの人をつまずかせます(その後、アクティブなルートでバブリングします)。 これをさらに悪化させるのは、エラーメッセージがログに記録されないことです。 親コンポーネントはエラーを飲み込むだけです。

一般的なルールは、アクションは現在のコンテキストで実行されるというものです。 非コンポーネントテンプレートの場合、そのコンテキストは現在のコントローラーですが、コンポーネントテンプレートの場合は、親コンポーネント(存在する場合)、またはコンポーネントがネストされていない場合は現在のコントローラーです。

したがって、上記の場合、 band-listコンポーネントは、 band-list-itemから受け取ったアクションを再起動して、コントローラーまたはルートにバブルアップする必要があります。

 // app/components/band-list.js export default Ember.Component.extend({ bands: [], favoriteAction: 'setFavoriteBand', actions: { setAsFavorite: function(band) { this.sendAction('favoriteAction', band); } } });

band-listbandsテンプレートで定義されている場合、 setFavoriteBandアクションは、 bandsコントローラーまたはbandsルート(またはその親ルートの1つ)で処理する必要があります。

エンバーの緩和計画

ネストのレベルが多い場合(たとえば、 band-list-item内にfav-buttonコンポーネントがある場合)、これはより複雑になることが想像できます。 メッセージを出すには、内側からいくつかのレイヤーに穴を開けて、各レベルで意味のある名前を定義する必要があります( setAsFavoritefavoriteActionfaveActionなど)。

これは、マスターブランチですでに利用可能であり、おそらく1.13に含まれる「改善されたアクションRFC」によって簡単になります。

上記の例は、次のように簡略化されます。

 // app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band setFavBand=(action "setFavoriteBand")}} {{/each}}
 // app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "setFavBand" band}}>Fave this</button>

よくある間違いその6:依存キーとして配列プロパティを使用する

Emberの計算されたプロパティは他のプロパティに依存しており、この依存関係は開発者が明示的に定義する必要があります。 役割の1つがadminである場合にのみ、trueである必要があるisAdminプロパティがあるとします。 これは人がそれを書くかもしれない方法です:

 isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles')

上記の定義では、 isAdminの値は、 roles配列オブジェクト自体が変更された場合にのみ無効になりますが、アイテムが既存の配列に追加または削除された場合は無効になりません。 追加と削除も再計算をトリガーする必要があることを定義する特別な構文があります。

 isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]')

よくある間違いその7:オブザーバーに優しい方法を使用しない

Common Mistake No. 6の(現在修正されている)例を拡張して、アプリケーションでUserクラスを作成しましょう。

 var User = Ember.Object.extend({ initRoles: function() { var roles = this.get('roles'); if (!roles) { this.set('roles', []); } }.on('init'), isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]') });

このようなUseradminの役割を追加すると、驚きます。

 var user = User.create(); user.get('isAdmin'); // => false user.get('roles').push('admin'); user.get('isAdmin'); // => false ?

問題は、ストックJavascriptメソッドが使用されている場合、オブザーバーが起動しない(したがって、計算されたプロパティが更新されない)ことです。 ブラウザでのObject.observeのグローバルな採用が改善されれば、これは変わる可能性がありますが、それまでは、Emberが提供する一連のメソッドを使用する必要があります。 現在の場合、 pushObjectは、 pushと同等のオブザーバーフレンドリーです。

 user.get('roles').pushObject('admin'); user.get('isAdmin'); // => true, finally!

よくある間違いその8:コンポーネントのプロパティで渡されるミューティング

アイテムの評価を表示し、アイテムの評価を設定できるstar-ratingコンポーネントがあるとします。 評価は、歌、本、またはサッカー選手のドリブルスキルに対して行うことができます。

テンプレートでは、次のように使用します。

 {{#each songs as |song|}} {{star-rating item=song rating=song.rating}} {{/each}}

さらに、コンポーネントが星を表示し、各ポイントに1つの完全な星が表示され、その後、最大評価まで空の星が表示されると仮定します。 星がクリックされると、コントローラーでsetたアクションが実行され、ユーザーが評価を更新したいと解釈する必要があります。 これを実現するために、次のコードを記述できます。

 // app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); item.set('rating', newRating); return item.save(); } } });

それで仕事は終わりますが、いくつか問題があります。 まず、渡されたアイテムにratingプロパティがあることを前提としているため、このコンポーネントを使用してLeo Messiのドリブルスキル(このプロパティはscoreと呼ばれる場合があります)を管理することはできません。

次に、コンポーネント内のアイテムの評価を変更します。 これは、特定のプロパティが変更される理由を理解するのが難しいシナリオにつながります。 同じテンプレートに別のコンポーネントがあり、その評価も使用されていると想像してください。たとえば、サッカー選手の平均スコアを計算するために使用されます。

このシナリオの複雑さを軽減するためのスローガンは、「データダウン、アクションアップ」(DDAU)です。 データは(ルートからコントローラー、コンポーネントに)渡される必要がありますが、コンポーネントはアクションを使用して、これらのデータの変更についてコンテキストに通知する必要があります。 では、ここでDDAUをどのように適用する必要がありますか?

評価を更新するために送信する必要があるアクション名を追加しましょう。

 {{#each songs as |song|}} {{star-rating item=song rating=song.rating setAction="updateRating"}} {{/each}}

次に、その名前を使用してアクションを送信します。

 // app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); this.sendAction('setAction', { item: this.get('item'), rating: newRating }); } } });

最後に、アクションはコントローラーまたはルートによってアップストリームで処理されます。ここで、アイテムの評価が更新されます。

 // app/routes/player.js export default Ember.Route.extend({ actions: { updateRating: function(params) { var skill = params.item, rating = params.rating; skill.set('score', rating); return skill.save(); } } });

これが発生すると、この変更はstar-ratingコンポーネントに渡されたバインディングを介して下向きに伝播され、結果として表示される完全な星の数が変更されます。

このように、コンポーネントでミューテーションが発生することはなく、アプリ固有の部分はルート内のアクションの処理のみであるため、コンポーネントの再利用性が損なわれることはありません。

サッカーのスキルにも同じコンポーネントを使用できます。

 {{#each player.skills as |skill|}} {{star-rating item=skill rating=skill.score setAction="updateSkill"}} {{/each}}

最後の言葉

ここで書いたものを含め、人々が犯した(または自分自身を犯した)間違いのいくつか(ほとんど?)は、2.xシリーズの早い段階で消えるか大幅に軽減されることに注意することが重要です。 Ember.jsの。

残っていることは上記の私の提案によって対処されているので、Ember 2.xで開発していると、それ以上エラーを起こす言い訳はありません! この記事をPDFとしてご希望の場合は、私のブログにアクセスして、投稿の下部にあるリンクをクリックしてください。

私について

私は2年前にEmber.jsでフロントエンドの世界に来ました、そして私はここにとどまります。 私はEmberに非常に熱心になり、ゲストの投稿と自分のブログの両方で熱心にブログを書き始め、会議で発表し始めました。 私は、Emberを学びたい人のために、Ember.jsでロックンロールという本を書きました。 ここからサンプルの章をダウンロードできます。