Ember.js Geliştiricilerinin Yaptığı En Yaygın 8 Hata
Yayınlanan: 2022-03-11Ember.js, karmaşık istemci tarafı uygulamaları oluşturmaya yönelik kapsamlı bir çerçevedir. İlkelerinden biri, "yapılandırma üzerine konvansiyon" ve çoğu web uygulamasında ortak olan çok büyük bir geliştirme bölümü olduğu ve bu nedenle bu günlük zorlukların çoğunu çözmenin tek bir en iyi yolu olduğu inancıdır. Ancak, doğru soyutlamayı bulmak ve tüm vakaları kapsamak zaman alır ve tüm topluluktan girdi alır. Akıl yürütmeye devam ederken, bir çözüm bulmaları gerektiğinde ellerimizi havaya kaldırıp herkesin kendi başının çaresine bakmasına izin vermek yerine, ana sorunun çözümünü doğru bir şekilde elde etmek için zaman ayırmak ve sonra onu çerçeveye oturtmak daha iyidir.
Ember.js, geliştirmeyi daha da kolaylaştırmak için sürekli gelişiyor. Ancak, herhangi bir gelişmiş çerçevede olduğu gibi, hala Ember geliştiricilerinin düşebileceği tuzaklar var. Aşağıdaki gönderiyle, bunlardan kaçınmak için bir harita sunmayı umuyorum. Hemen atlayalım!
Yaygın Hata No. 1: Tüm Bağlam Nesneleri Geçtiğinde Model Kancasının Ateşlenmesini Beklemek
Diyelim ki uygulamamızda aşağıdaki rotalar var:
Router.map(function() { this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
band
yolunun dinamik bir segmenti var, id
. Uygulama /bands/24
gibi bir URL ile yüklendiğinde, 24
ilgili rotanın model
kancasına, band
geçirilir. Model kancası, daha sonra şablonda kullanılabilecek bir nesne (veya bir dizi nesne) oluşturmak için segmenti seri durumdan çıkarma rolüne sahiptir:
// app/routes/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); // params.id is '24' } });
Çok uzak çok iyi. Ancak, uygulamayı tarayıcının gezinme çubuğundan yüklemekten başka rotaları girmenin başka yolları da vardır. Bunlardan biri, şablonlardan link-to
yardımcısı kullanmaktır. Aşağıdaki pasaj, bir grup listesinden geçer ve ilgili band
rotalarına bir bağlantı oluşturur:
{{#each bands as |band|}} {{link-to band.name "band" band}} {{/each}}
Link-to için son argüman, band
, rota için dinamik segmenti dolduran bir nesnedir ve dolayısıyla id
, rotanın id segmenti olur. Birçok kişinin düştüğü tuzak, model zaten bilindiği ve geçildiği için bu durumda model kancasının çağrılmaması. Mantıklı ve sunucuya bir istek kaydedebilir, ancak kuşkusuz, değil. sezgisel. Bunu aşmanın ustaca bir yolu, nesnenin kendisini değil, kimliğini geçmektir:
{{#each bands as |band|}} {{link-to band.name "band" band.id}} {{/each}}
Ember'in Azaltma Planı
Yönlendirilebilir bileşenler, kısa süre içinde, muhtemelen 2.1 veya 2.2 sürümünde Ember'a gelecek. İndiklerinde, dinamik segmentli bir rotaya nasıl geçilirse geçilsin, model kancası her zaman çağrılır. İlgili RFC'yi buradan okuyun.
Yaygın Hata No. 2: Rotaya Dayalı Denetleyicilerin Singleton Olduğunu Unutmak
Ember.js'deki rotalar, ilgili şablon için bağlam görevi gören denetleyicilerde özellikleri ayarlar. Bu kontrolörler tekildir ve sonuç olarak üzerlerinde tanımlanan herhangi bir durum, kontrolör artık aktif olmadığında bile devam eder.
Bu gözden kaçması çok kolay bir şey ve ben de buna rastladım. Benim durumumda, gruplar ve şarkılar içeren bir müzik kataloğu uygulamam vardı. songs
denetleyicisindeki songCreationStarted
bayrağı, kullanıcının belirli bir grup için bir şarkı oluşturmaya başladığını gösteriyordu. Sorun şuydu ki, kullanıcı daha sonra başka bir gruba songCreationStarted
değerinin devam etmesi ve yarısı bitmiş şarkının diğer grup için olması gibi görünüyordu, bu da kafa karıştırıcıydı.
Çözüm, oyalanmak istemediğimiz denetleyici özelliklerini manuel olarak sıfırlamaktır. Bunu yapmak için olası bir yer, setupController
kancasından sonraki tüm geçişlerde çağrılan ilgili rotanın afterModel
(adından da anlaşılacağı gibi, model
kancasından sonra gelir):
// app/routes/band.js export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); controller.set('songCreationStarted', false); } });
Ember'in Azaltma Planı
Yine, yönlendirilebilir bileşenlerin şafağı bu sorunu çözecek ve denetleyicilere tamamen son verecektir. Yönlendirilebilir bileşenlerin avantajlarından biri, daha tutarlı bir yaşam döngüsüne sahip olmaları ve rotalarından ayrılırken her zaman parçalanmalarıdır. Geldiklerinde, yukarıdaki sorun ortadan kalkacaktır.
Yaygın Hata No. 3: setupController'da Varsayılan Uygulamayı setupController
Ember'deki rotalar, uygulamaya özel davranışı tanımlamak için bir avuç yaşam döngüsü kancasına sahiptir. İlgili şablon için verileri almak için kullanılan model
ve şablonun bağlamı olan denetleyiciyi ayarlamak için setupController
zaten gördük.
Bu sonuncusu, setupController
, denetleyicinin model
özelliği olarak model
kancasından modeli atayan mantıklı bir varsayılana sahiptir:
// ember-routing/lib/system/route.js setupController(controller, context, transition) { if (controller && (context !== undefined)) { set(controller, 'model', context); } }
( context
, yukarıda model
dediğim şey için ember-routing
paketi tarafından kullanılan addır)
setupController
kancası, denetleyicinin durumunu sıfırlamak gibi çeşitli amaçlar için geçersiz kılınabilir (yukarıdaki Ortak Hata No. 2'de olduğu gibi). Ancak, yukarıda Ember.Route'da kopyaladığım ana uygulamayı çağırmayı unutursanız, denetleyicinin model
özelliği ayarlanmayacağından, kişi uzun bir kafa kaşıma oturumu içinde olabilir. Bu yüzden her zaman this._super(controller, model)
:
export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); // put the custom setup here } });
Ember'in Azaltma Planı
Daha önce belirtildiği gibi, kontrolörler ve onlarla birlikte setupController
kancası yakında ortadan kalkacak, bu nedenle bu tuzak artık bir tehdit olmayacak. Ancak, burada atalardaki uygulamalara dikkat edilmesi gereken daha büyük bir ders var. Ember'deki tüm nesnelerin anası olan Ember.Object
içinde tanımlanan init
işlevi, dikkat etmeniz gereken başka bir örnektir.
Yaygın Hata No. 4: this.model'i Ana Olmayan this.modelFor
Kullanmak
Ember yönlendirici, URL'yi işlerken her rota segmenti için modeli çözer. Diyelim ki uygulamamızda aşağıdaki rotalar var:
Router.map({ this.route('bands', function() { this.route('band', { path: ':id' }, function() { this.route('songs'); }); }); });
/bands/24/songs
URL'si verildiğinde, bands
, bands.band
ve ardından bands.band.songs
model
kancası bu sırayla çağrılır. Rota API'si, modeli ana rotalardan birinden almak için alt rotalarda kullanılabilen özellikle kullanışlı bir modelFor
yöntemine sahiptir, çünkü bu model kesinlikle o noktada çözülmüştür.
Örneğin, aşağıdaki kod, band nesnesini bands.band
yolunda getirmenin geçerli bir yoludur:
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); return bands.filterBy('id', params.id); } });
Ancak yaygın bir hata, modelFor'da rotanın bir ebeveyni olmayan bir rota adı kullanmaktır. Yukarıdaki örnekteki rotalar biraz değiştirilmişse:
Router.map({ this.route('bands'); this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
URL'de belirtilen bandı getirme yöntemimiz, bands
rotası artık bir üst öğe olmadığı ve dolayısıyla modeli çözülmediği için bozulur.
// 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! } });
Çözüm, modelFor
yalnızca üst rotalar için kullanmak ve modelFor
kullanılamadığında gerekli verileri almak için mağazadan alma gibi diğer araçları kullanmaktır.
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); } });
5 No'lu Genel Hata: Bir Bileşen Eyleminin Başlatıldığı Bağlamı Yanlış Anlamak
İç içe bileşenler, her zaman Ember'in akıl yürütmesi en zor kısımlarından biri olmuştur. Ember 1.10'da blok parametrelerinin eklenmesiyle, bu karmaşıklığın çoğu giderildi, ancak birçok durumda, bir alt bileşenden tetiklenen bir eylemin hangi bileşende tetikleneceğini bir bakışta görmek hala zor.
İçinde band-list-items
listesi öğeleri olan bir band-list
bileşenimiz olduğunu varsayalım ve her grubu listede favori olarak işaretleyebiliriz.
// app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band faveAction="setAsFavorite"}} {{/each}}
Kullanıcı düğmeye tıkladığında çağrılması gereken eylem adı, band-list-item
bileşenine iletilir ve faveAction
özelliğinin değeri olur.

Şimdi band-list-item
şablonunu ve bileşen tanımını görelim:
// 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')); } } });
Kullanıcı "Bunu favorilere ekle" düğmesini tıkladığında, bileşenin üst bileşeni olan band-list
geçirilen faveAction
(yukarıdaki durumda setAsFavorite
) tetikleyen faveBand
eylemi tetiklenir.
Eylemin, rotaya dayalı şablonlardaki eylemlerin denetleyicide olduğu gibi (ve ardından etkin rotalarda köpürerek) tetiklenmesini bekledikleri için bu, birçok insanı tetikler. Bunu daha da kötüleştiren şey, hiçbir hata mesajının günlüğe kaydedilmemesidir; ana bileşen sadece hatayı yutar.
Genel kural, eylemlerin mevcut bağlamda tetiklenmesidir. Bileşen olmayan şablonlar söz konusu olduğunda, bu bağlam geçerli denetleyicidir, bileşen şablonları söz konusu olduğunda, ana bileşendir (varsa), veya bileşen iç içe değilse yine geçerli denetleyicidir.
Bu nedenle, yukarıdaki durumda, band-list
bileşeninin, kontrol cihazına veya rotaya aktarılabilmesi için band-list-item
alınan eylemi yeniden başlatması gerekir.
// app/components/band-list.js export default Ember.Component.extend({ bands: [], favoriteAction: 'setFavoriteBand', actions: { setAsFavorite: function(band) { this.sendAction('favoriteAction', band); } } });
band-list
bands
şablonunda tanımlanmışsa, setFavoriteBand
eyleminin bands
denetleyicisinde veya bands
yolunda (veya üst yollarından birinde) işlenmesi gerekir.
Ember'in Azaltma Planı
Daha fazla iç içe yerleştirme düzeyi varsa (örneğin, band-list-item
içinde bir fav-button
bileşenine sahip olarak) bunun daha karmaşık hale geldiğini hayal edebilirsiniz. Her düzeyde anlamlı adlar tanımlayarak ( setAsFavorite
, favoriteAction
, faveAction
, vb.)
Bu, ana dalda zaten mevcut olan ve muhtemelen 1.13'e dahil edilecek olan "Geliştirilmiş Eylemler RFC" ile daha basit hale getirilmiştir.
Yukarıdaki örnek daha sonra şu şekilde basitleştirilecektir:
// 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 No'lu Genel Hata: Dizi Özelliklerini Bağımlı Anahtarlar Olarak Kullanma
Ember'in hesaplanan özellikleri diğer özelliklere bağlıdır ve bu bağımlılığın geliştirici tarafından açıkça tanımlanması gerekir. Sadece ve sadece rollerden biri admin
ise doğru olması gereken bir isAdmin
özelliğimiz olduğunu varsayalım. Bir kişi bunu şöyle yazabilir:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles')
Yukarıdaki tanımla, isAdmin
değeri yalnızca roles
dizisi nesnesinin kendisi değiştiğinde geçersiz olur, ancak mevcut diziye öğeler eklendiğinde veya kaldırıldığında geçersiz olur. Eklemelerin ve çıkarmaların yeniden hesaplamayı tetikleyeceğini tanımlayan özel bir sözdizimi vardır:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]')
7 No'lu Ortak Hata: Gözlemci Dostu Yöntemleri Kullanmamak
6 numaralı Ortak Hata'daki (şimdi düzeltildi) örneği genişletelim ve uygulamamızda bir Kullanıcı sınıfı oluşturalım.
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.[]') });
Böyle bir User
admin
rolünü eklediğimizde, bir sürprizle karşı karşıyayız:
var user = User.create(); user.get('isAdmin'); // => false user.get('roles').push('admin'); user.get('isAdmin'); // => false ?
Sorun şu ki, stok Javascript yöntemleri kullanılırsa gözlemciler ateş etmeyecek (ve dolayısıyla hesaplanan özellikler güncellenmeyecek). Tarayıcılarda Object.observe
global olarak benimsenmesi iyileşirse bu değişebilir, ancak o zamana kadar Ember'in sağladığı yöntemler kümesini kullanmak zorundayız. Mevcut durumda, pushObject
, push
öğesinin gözlemci dostu eşdeğeridir:
user.get('roles').pushObject('admin'); user.get('isAdmin'); // => true, finally!
8 Numaralı Genel Hata: Bileşenlerdeki Özelliklerde Geçirilen Mutasyon
Bir öğenin derecelendirmesini görüntüleyen ve öğenin derecelendirmesinin ayarlanmasına izin veren bir star-rating
bileşenimiz olduğunu hayal edin. Derecelendirme bir şarkı, kitap veya bir futbolcunun top sürme becerisi için olabilir.
Şablonunuzda şöyle kullanırsınız:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating}} {{/each}}
Ayrıca, bileşenin maksimum derecelendirmeye kadar her nokta için bir tam yıldız ve bundan sonra boş yıldızlar olmak üzere yıldızları görüntülediğini varsayalım. Bir yıldız tıklandığında, denetleyicide bir set
eylemi başlatılır ve bu, kullanıcının derecelendirmeyi güncellemek istediği şeklinde yorumlanmalıdır. Bunu başarmak için aşağıdaki kodu yazabiliriz:
// 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(); } } });
Bu işi hallederdi, ancak bununla ilgili birkaç sorun var. İlk olarak, geçirilen öğenin bir rating
özelliğine sahip olduğunu varsayar ve bu nedenle Leo Messi'nin top sürme becerisini yönetmek için bu bileşeni kullanamayız (burada bu özellik score
olarak adlandırılabilir).
İkincisi, öğenin bileşendeki derecesini değiştirir. Bu, belirli bir özelliğin neden değiştiğini görmenin zor olduğu senaryolara yol açar. Aynı şablonda, örneğin bir futbolcunun ortalama skorunu hesaplamak için bu derecelendirmenin de kullanıldığı başka bir bileşenimiz olduğunu hayal edin.
Bu senaryonun karmaşıklığını azaltmanın sloganı "Veri azaldı, eylemler arttı" (DDAU). Veriler aktarılmalıdır (rotadan denetleyiciye, bileşenlere), bileşenler ise bu verilerdeki değişiklikler hakkında bağlamlarını bildirmek için eylemleri kullanmalıdır. Peki burada DDAU nasıl uygulanmalıdır?
Derecelendirmeyi güncellemek için gönderilmesi gereken bir işlem adı ekleyelim:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating setAction="updateRating"}} {{/each}}
Ardından eylemi göndermek için bu adı kullanın:
// 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 }); } } });
Son olarak, eylem kontrolör veya rota tarafından yukarı akışta işlenir ve öğenin derecelendirmesi burada güncellenir:
// 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(); } } });
Bu olduğunda, bu değişiklik, star-rating
bileşenine geçirilen bağlama yoluyla aşağı doğru yayılır ve sonuç olarak görüntülenen tam yıldız sayısı değişir.
Bu şekilde, bileşenlerde mutasyon meydana gelmez ve uygulamaya özel tek kısım, eylemin rotadaki işlenmesi olduğundan, bileşenin yeniden kullanılabilirliği zarar görmez.
Aynı bileşeni futbol becerileri için de kullanabiliriz:
{{#each player.skills as |skill|}} {{star-rating item=skill rating=skill.score setAction="updateSkill"}} {{/each}}
Son sözler
Burada yazdıklarım da dahil olmak üzere, insanların işlediğini (veya kendimin yaptığını) gördüğüm bazı (çoğu?) hataların 2.x serisinin başlarında ortadan kalkacağını veya büyük ölçüde azaltılacağını belirtmek önemlidir. Ember.js'nin
Geriye kalanlar yukarıdaki önerilerimde ele alınmaktadır, bu nedenle Ember 2.x'te bir kez geliştirdiğinizde, daha fazla hata yapmak için hiçbir bahaneniz kalmayacak! Bu makaleyi pdf olarak istiyorsanız, bloguma gidin ve gönderinin altındaki bağlantıya tıklayın.
Benim hakkımda
Ön uç dünyasına iki yıl önce Ember.js ile geldim ve burada kalmak için buradayım. Ember hakkında o kadar heveslendim ki, hem misafir yazılarında hem de kendi blogumda yoğun bir şekilde blog yazmaya ve konferanslarda sunum yapmaya başladım. Ember öğrenmek isteyenler için Ember.js ile Rock and Roll adlı bir kitap bile yazdım. Örnek bir bölümü buradan indirebilirsiniz.