8 Kesalahan Paling Umum yang Dilakukan Pengembang Ember.js
Diterbitkan: 2022-03-11Ember.js adalah kerangka kerja komprehensif untuk membangun aplikasi sisi klien yang kompleks. Salah satu prinsipnya adalah "konvensi atas konfigurasi," dan keyakinan bahwa ada bagian yang sangat besar dari pengembangan yang umum untuk sebagian besar aplikasi web, dan dengan demikian satu-satunya cara terbaik untuk menyelesaikan sebagian besar tantangan sehari-hari ini. Namun, menemukan abstraksi yang tepat, dan mencakup semua kasus, membutuhkan waktu dan masukan dari seluruh komunitas. Sebagaimana alasannya, lebih baik meluangkan waktu untuk mendapatkan solusi untuk masalah inti dengan benar, dan kemudian memasukkannya ke dalam kerangka kerja, daripada mengangkat tangan dan membiarkan semua orang berjuang sendiri ketika mereka perlu menemukan solusi.
Ember.js terus berkembang untuk membuat pengembangan lebih mudah. Namun, seperti halnya kerangka kerja lanjutan lainnya, masih ada jebakan yang mungkin dialami oleh pengembang Ember. Dengan posting berikut, saya berharap dapat memberikan peta untuk menghindari ini. Ayo langsung masuk!
Kesalahan Umum No. 1: Mengharapkan Kait Model Terbakar Ketika Semua Objek Konteks Dilewati
Mari kita asumsikan kita memiliki rute berikut dalam aplikasi kita:
Router.map(function() { this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
Rute band
memiliki segmen dinamis, id
. Saat aplikasi dimuat dengan URL seperti /bands/24
, 24
diteruskan ke model
hook dari rute yang sesuai, band
. Model hook memiliki peran deserializing segmen untuk membuat objek (atau array objek) yang kemudian dapat digunakan dalam template:
// app/routes/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); // params.id is '24' } });
Sejauh ini bagus. Namun, ada cara lain untuk memasukkan rute selain memuat aplikasi dari navbar browser. Salah satunya adalah menggunakan link-to
helper dari template. Cuplikan berikut menelusuri daftar band dan membuat tautan ke rute band
masing-masing:
{{#each bands as |band|}} {{link-to band.name "band" band}} {{/each}}
Argumen terakhir untuk link-to, band
, adalah objek yang mengisi segmen dinamis untuk rute, dan dengan demikian id
-nya menjadi segmen id untuk rute. Jebakan yang banyak orang jatuh ke dalam adalah bahwa model hook tidak dipanggil dalam kasus itu, karena model sudah diketahui dan telah diteruskan. Masuk akal dan mungkin menyimpan permintaan ke server tetapi, memang, tidak intuitif. Cara cerdik untuk melewatinya adalah dengan memasukkan, bukan objek itu sendiri, tetapi id-nya:
{{#each bands as |band|}} {{link-to band.name "band" band.id}} {{/each}}
Rencana Mitigasi Ember
Komponen yang dapat dirutekan akan segera hadir di Ember, mungkin dalam versi 2.1 atau 2.2. Ketika mereka mendarat, hook model akan selalu dipanggil, tidak peduli bagaimana transisi ke rute dengan segmen dinamis. Baca RFC yang sesuai di sini.
Kesalahan Umum No. 2: Melupakan Bahwa Pengendali Berbasis Rute Adalah Lajang
Rute di Ember.js menyiapkan properti pada pengontrol yang berfungsi sebagai konteks untuk template yang sesuai. Pengontrol ini adalah lajang dan akibatnya setiap status yang ditentukan pada mereka tetap ada bahkan ketika pengontrol tidak lagi aktif.
Ini adalah sesuatu yang sangat mudah untuk diabaikan dan saya juga tersandung pada hal ini. Dalam kasus saya, saya memiliki aplikasi katalog musik dengan band dan lagu. Bendera songCreationStarted
pada pengontrol songs
menunjukkan bahwa pengguna telah mulai membuat lagu untuk band tertentu. Masalahnya adalah jika pengguna kemudian beralih ke band lain, nilai songCreationStarted
tetap ada, dan sepertinya lagu setengah jadi itu untuk band lain, yang membingungkan.
Solusinya adalah dengan mereset secara manual property controller yang tidak ingin kita berlama-lama. Satu tempat yang memungkinkan untuk melakukan ini adalah hook setupController
dari rute yang sesuai, yang dipanggil pada semua transisi setelah hook afterModel
(yang, seperti namanya, muncul setelah hook model
):
// app/routes/band.js export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); controller.set('songCreationStarted', false); } });
Rencana Mitigasi Ember
Sekali lagi, komponen yang dapat dirutekan akan menyelesaikan masalah ini, mengakhiri pengontrol sama sekali. Salah satu keuntungan dari komponen yang dapat dirutekan adalah bahwa mereka memiliki siklus hidup yang lebih konsisten dan selalu rusak saat bertransisi dari rute mereka. Ketika mereka tiba, masalah di atas akan hilang.
Kesalahan Umum No. 3: Tidak Memanggil Implementasi Default di setupController
Rute di Ember memiliki beberapa kait siklus hidup untuk menentukan perilaku khusus aplikasi. Kita sudah melihat model
yang digunakan untuk mengambil data untuk template yang sesuai dan setupController
, untuk menyiapkan controller, konteks template.
Yang terakhir ini, setupController
, memiliki default yang masuk akal, yang menetapkan model, dari kait model
sebagai properti model
dari controller:
// ember-routing/lib/system/route.js setupController(controller, context, transition) { if (controller && (context !== undefined)) { set(controller, 'model', context); } }
( context
adalah nama yang digunakan oleh paket ember-routing
untuk apa yang saya sebut model
di atas)
Kait setupController
dapat diganti untuk beberapa tujuan, seperti menyetel ulang status pengontrol (seperti pada Kesalahan Umum No. 2 di atas). Namun, jika seseorang lupa memanggil implementasi induk yang saya salin di atas di Ember.Route, seseorang dapat terlibat dalam sesi yang panjang, karena pengontrol tidak akan memiliki set properti model
. Jadi selalu panggil this._super(controller, model)
:
export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); // put the custom setup here } });
Rencana Mitigasi Ember
Seperti yang dinyatakan sebelumnya, pengontrol, dan bersama mereka, kait setupController
, akan segera hilang, jadi perangkap ini tidak akan menjadi ancaman lagi. Namun, ada pelajaran yang lebih besar yang bisa dipetik di sini, yaitu memperhatikan pelaksanaan di leluhur. Fungsi init
, yang didefinisikan di Ember.Object
, induk dari semua objek di Ember, adalah contoh lain yang harus Anda perhatikan.
Kesalahan Umum No. 4: Menggunakan this.modelFor
dengan Rute Non-induk
Router Ember menyelesaikan model untuk setiap segmen rute saat memproses URL. Mari kita asumsikan kita memiliki rute berikut dalam aplikasi kita:
Router.map({ this.route('bands', function() { this.route('band', { path: ':id' }, function() { this.route('songs'); }); }); });
Diberikan URL /bands/24/songs
, model
hook dari bands
, bands.band
dan kemudian bands.band.songs
dipanggil, dalam urutan ini. API rute memiliki metode yang sangat berguna, modelFor
, yang dapat digunakan di rute anak untuk mengambil model dari salah satu rute induk, karena model itu pasti telah diselesaikan pada saat itu.
Misalnya, kode berikut adalah cara yang valid untuk mengambil objek band di rute 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); } });
Namun, kesalahan umum adalah menggunakan nama rute dalam modelFor yang bukan merupakan induk dari rute tersebut. Jika rute dari contoh di atas sedikit diubah:
Router.map({ this.route('bands'); this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
Metode kami untuk mengambil pita yang ditunjuk dalam URL akan rusak, karena rute bands
tidak lagi menjadi induk dan dengan demikian modelnya belum diselesaikan.
// 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! } });
Solusinya adalah menggunakan modelFor
hanya untuk rute induk, dan menggunakan cara lain untuk mengambil data yang diperlukan ketika modelFor
tidak dapat digunakan, seperti mengambil dari toko.
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); } });
Kesalahan Umum No. 5: Salah Menganggap Konteks Tindakan Komponen Diaktifkan
Komponen bersarang selalu menjadi salah satu bagian Ember yang paling sulit untuk dipikirkan. Dengan diperkenalkannya parameter blok di Ember 1.10, banyak kerumitan ini telah dihilangkan, tetapi dalam banyak situasi, masih sulit untuk melihat sekilas komponen apa yang memicu tindakan, yang dipicu dari komponen turunan.
Mari kita asumsikan kita memiliki komponen band-list
yang memiliki band-list-items
di dalamnya, dan kita dapat menandai setiap band sebagai favorit dalam daftar.
// app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band faveAction="setAsFavorite"}} {{/each}}
Nama tindakan yang harus dipanggil saat pengguna mengklik tombol diteruskan ke komponen band-list-item
, dan menjadi nilai properti faveAction
-nya.

Sekarang mari kita lihat definisi template dan komponen dari 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')); } } });
Ketika pengguna mengklik tombol “Fave this”, tindakan faveBand
akan terpicu, yang mengaktifkan faveAction
komponen yang diteruskan ( setAsFavorite
, dalam kasus di atas), pada komponen induknya , band-list
.
Itu membuat banyak orang tersandung karena mereka mengharapkan tindakan itu dipecat dengan cara yang sama seperti tindakan dari templat yang digerakkan oleh rute, pada pengontrol (dan kemudian menggelegak pada rute yang aktif). Yang membuat ini lebih buruk adalah tidak ada pesan kesalahan yang dicatat; komponen induk hanya menelan kesalahan.
Aturan umumnya adalah bahwa tindakan dipicu pada konteks saat ini. Dalam kasus templat non-komponen, konteks itu adalah pengontrol saat ini, sedangkan dalam kasus templat komponen, itu adalah komponen induk (jika ada), atau lagi pengontrol saat ini jika komponen tidak bersarang.
Jadi dalam kasus di atas, komponen band-list
harus mengaktifkan kembali aksi yang diterima dari band-list-item
untuk menggelembungkannya ke controller atau route.
// app/components/band-list.js export default Ember.Component.extend({ bands: [], favoriteAction: 'setFavoriteBand', actions: { setAsFavorite: function(band) { this.sendAction('favoriteAction', band); } } });
Jika daftar band-list
didefinisikan dalam template bands
, maka tindakan setFavoriteBand
harus ditangani di pengontrol bands
atau rute bands
(atau salah satu rute induknya).
Rencana Mitigasi Ember
Anda dapat membayangkan bahwa ini menjadi lebih kompleks jika ada lebih banyak level bersarang (misalnya, dengan memiliki komponen fav-button
di dalam band-list-item
). Anda harus mengebor beberapa lapisan dari dalam untuk mengeluarkan pesan Anda, menentukan nama yang bermakna di setiap level ( setAsFavorite
, favoriteAction
, faveAction
, dll.)
Ini dibuat lebih sederhana dengan "Tindakan yang Ditingkatkan RFC", yang sudah tersedia di cabang master, dan mungkin akan disertakan dalam 1.13.
Contoh di atas kemudian akan disederhanakan menjadi:
// 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>
Kesalahan Umum No. 6: Menggunakan Properti Array Sebagai Kunci Dependen
Properti yang dihitung Ember bergantung pada properti lain, dan ketergantungan ini perlu didefinisikan secara eksplisit oleh pengembang. Katakanlah kita memiliki properti isAdmin
yang seharusnya benar jika dan hanya jika salah satu perannya adalah admin
. Ini adalah bagaimana seseorang dapat menulisnya:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles')
Dengan definisi di atas, nilai isAdmin
hanya menjadi tidak valid jika objek array roles
itu sendiri berubah, tetapi tidak jika item ditambahkan atau dihapus ke array yang ada. Ada sintaks khusus untuk menentukan bahwa penambahan dan penghapusan juga harus memicu penghitungan ulang:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]')
Kesalahan Umum No. 7: Tidak Menggunakan Metode yang Ramah Pengamat
Mari kita perluas contoh (sekarang diperbaiki) dari Kesalahan Umum No. 6, dan buat kelas Pengguna di aplikasi kita.
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.[]') });
Saat kami menambahkan peran admin
ke User
, kami terkejut:
var user = User.create(); user.get('isAdmin'); // => false user.get('roles').push('admin'); user.get('isAdmin'); // => false ?
Masalahnya adalah bahwa pengamat tidak akan menyala (dan dengan demikian properti yang dihitung tidak akan diperbarui) jika metode Javascript stok digunakan. Ini mungkin berubah jika adopsi global Object.observe
di browser meningkat, tetapi sampai saat itu, kita harus menggunakan kumpulan metode yang disediakan Ember. Dalam kasus saat ini, pushObject
adalah padanan yang ramah pengamat dari push
:
user.get('roles').pushObject('admin'); user.get('isAdmin'); // => true, finally!
Kesalahan Umum No. 8: Mutasi yang Dilewati dalam Properti di Komponen
Bayangkan kita memiliki komponen star-rating
yang menampilkan peringkat item dan memungkinkan pengaturan peringkat item. Rating tersebut bisa untuk sebuah lagu, buku atau keterampilan menggiring bola pemain sepak bola.
Anda akan menggunakannya seperti ini di template Anda:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating}} {{/each}}
Mari kita asumsikan lebih lanjut bahwa komponen menampilkan bintang, satu bintang penuh untuk setiap titik, dan bintang kosong setelah itu, hingga peringkat maksimum. Ketika sebuah bintang diklik, aksi yang set
akan diaktifkan pada pengontrol, dan itu harus ditafsirkan sebagai pengguna yang ingin memperbarui peringkat. Kita bisa menulis kode berikut untuk mencapai ini:
// 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(); } } });
Itu akan menyelesaikan pekerjaan, tetapi ada beberapa masalah dengan itu. Pertama, diasumsikan bahwa item yang dioper memiliki properti rating
, jadi kita tidak bisa menggunakan komponen ini untuk mengatur skill dribble Leo Messi (dimana properti ini bisa disebut score
).
Kedua, mengubah peringkat item dalam komponen. Ini mengarah ke skenario di mana sulit untuk melihat mengapa properti tertentu berubah. Bayangkan kita memiliki komponen lain dalam template yang sama di mana rating itu juga digunakan, misalnya, untuk menghitung skor rata-rata untuk pemain sepak bola.
Slogan untuk mengurangi kompleksitas skenario ini adalah “Data down, actions up” (DDAU). Data harus diturunkan (dari rute ke pengontrol ke komponen), sementara komponen harus menggunakan tindakan untuk memberi tahu konteksnya tentang perubahan dalam data ini. Jadi bagaimana seharusnya DDAU diterapkan di sini?
Mari tambahkan nama tindakan yang harus dikirim untuk memperbarui peringkat:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating setAction="updateRating"}} {{/each}}
Dan kemudian gunakan nama itu untuk mengirim tindakan ke atas:
// 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 }); } } });
Terakhir, tindakan ditangani di hulu, oleh pengontrol atau rute, dan di sinilah peringkat item diperbarui:
// 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(); } } });
Ketika ini terjadi, perubahan ini disebarkan ke bawah melalui pengikatan yang diteruskan ke komponen star-rating
, dan jumlah bintang penuh yang ditampilkan berubah sebagai hasilnya.
Dengan cara ini, mutasi tidak terjadi pada komponen, dan karena satu-satunya bagian khusus aplikasi adalah penanganan tindakan dalam rute, penggunaan kembali komponen tidak terganggu.
Kita juga bisa menggunakan komponen yang sama untuk keterampilan sepak bola:
{{#each player.skills as |skill|}} {{star-rating item=skill rating=skill.score setAction="updateSkill"}} {{/each}}
Kata-kata Terakhir
Penting untuk dicatat bahwa beberapa (kebanyakan?) kesalahan yang saya lihat dilakukan orang (atau dilakukan sendiri), termasuk yang telah saya tulis di sini, akan hilang atau dikurangi secara signifikan di awal seri 2.x dari Ember.js.
Apa yang tersisa ditangani oleh saran saya di atas, jadi setelah Anda mengembangkan di Ember 2.x, Anda tidak akan punya alasan untuk membuat kesalahan lagi! Jika Anda ingin artikel ini dalam bentuk pdf, kunjungi blog saya dan klik tautan di bagian bawah postingan.
Tentang saya
Saya datang ke dunia front-end dengan Ember.js dua tahun lalu, dan saya di sini untuk tinggal. Saya menjadi sangat antusias dengan Ember sehingga saya mulai ngeblog secara intens baik di posting tamu dan di blog saya sendiri, serta presentasi di konferensi. Saya bahkan menulis sebuah buku, Rock and Roll dengan Ember.js , untuk siapa saja yang ingin belajar Ember. Anda dapat mengunduh bab sampel di sini.