Bagaimana menangani inisialisasi dan rendering subview di Backbone.js?

199

Saya memiliki tiga cara berbeda untuk menginisialisasi dan membuat tampilan dan subview, dan masing-masing memiliki masalah yang berbeda. Saya ingin tahu apakah ada cara yang lebih baik yang menyelesaikan semua masalah:


Skenario Satu:

Inisialisasi anak-anak dalam fungsi inisialisasi orang tua. Dengan cara ini, tidak semua macet di render sehingga ada lebih sedikit pemblokiran pada rendering.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.render().appendTo(this.$('.container-placeholder');
}

Masalah:

  • Masalah terbesar adalah bahwa memanggil render pada orang tua untuk kedua kalinya akan menghapus semua binding acara anak-anak. (Ini karena cara kerja jQuery $.html().) Ini dapat dikurangi dengan menelepon this.child.delegateEvents().render().appendTo(this.$el);saja, tetapi kemudian yang pertama, dan yang paling sering, Anda melakukan lebih banyak pekerjaan yang tidak perlu.

  • Dengan menambahkan anak-anak, Anda memaksa fungsi render untuk memiliki pengetahuan tentang struktur DOM orang tua sehingga Anda mendapatkan pemesanan yang Anda inginkan. Yang berarti mengubah templat mungkin memerlukan memperbarui fungsi render tampilan.


Skenario Dua:

Inisialisasi anak-anak di initialize()masih orang tua , tetapi alih-alih menambahkan, gunakan setElement().delegateEvents()untuk mengatur anak ke elemen dalam template orang tua.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

Masalah:

  • Ini membuat yang delegateEvents()diperlukan sekarang, yang sedikit negatif di atasnya hanya diperlukan pada panggilan berikutnya dalam skenario pertama.

Skenario Tiga:

Inisialisasi anak-anak dalam metode orangtua render()sebagai gantinya.

initialize : function () {

    //parent init stuff
},

render : function () {

    this.$el.html(this.template());

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

Masalah:

  • Ini berarti bahwa fungsi render sekarang harus diikat dengan semua logika inisialisasi juga.

  • Jika saya mengedit status salah satu tampilan anak, dan kemudian memanggil render pada orang tua, anak yang sama sekali baru akan dibuat dan semua status saat ini akan hilang. Yang juga sepertinya bisa jadi tidak pasti karena kebocoran memori.


Sangat penasaran untuk membuat orang lain menerima ini. Skenario apa yang akan Anda gunakan? atau adakah magis keempat yang memecahkan semua masalah ini?

Apakah Anda pernah melacak status yang diberikan untuk Tampilan? Ucapkan renderedBeforebendera? Tampak sangat janky.

Ian Storm Taylor
sumber
1
Saya biasanya tidak memungut referensi untuk pandangan anak pada pandangan orang tua menyebabkan sebagian besar komunikasi terjadi melalui model / koleksi dan acara yang dipicu oleh perubahan ini. Meskipun case ke 3 paling dekat dengan apa yang saya gunakan sebagian besar waktu. Kasus 1 di mana masuk akal. Juga sebagian besar waktu Anda tidak boleh merender ulang seluruh tampilan melainkan bagian yang telah berubah
Tom Tu
Tentu, saya pasti membuat hanya bagian yang diubah bila mungkin, tetapi meskipun demikian saya pikir fungsi render harus tersedia dan tidak merusak. Bagaimana Anda menangani tidak memiliki referensi ke anak-anak di orangtua?
Ian Storm Taylor
Biasanya saya mendengarkan acara pada model yang terkait dengan pandangan anak - jika saya ingin melakukan sesuatu yang lebih khusus maka saya mengikat acara ke subview. Jika 'komunikasi' semacam ini diperlukan maka saya biasanya membuat metode pembantu untuk membuat subview yang mengikat acara, dll.
Tom Tu
1
Untuk diskusi terkait level yang lebih tinggi, lihat: stackoverflow.com/questions/10077185/… .
Ben Roberts
2
Dalam Skenario Dua, mengapa perlu menelepon delegateEvents()setelah setElement()? Sesuai dokumen: "... dan pindahkan acara yang didelegasikan tampilan dari elemen lama ke yang baru", setElementmetode itu sendiri harus menangani delegasi acara kembali.
rdamborsky

Jawaban:

260

Ini pertanyaan yang bagus. Backbone bagus karena kurangnya asumsi yang dibuatnya, tetapi itu berarti Anda harus (memutuskan bagaimana) mengimplementasikan hal-hal seperti ini sendiri. Setelah melihat-lihat barang saya sendiri, saya menemukan bahwa saya (jenis) menggunakan campuran skenario 1 dan skenario 2. Saya tidak berpikir skenario ajaib ke-4 ada karena, cukup sederhana, semua yang Anda lakukan dalam skenario 1 & 2 harus selesai

Saya pikir akan lebih mudah untuk menjelaskan bagaimana saya ingin menanganinya dengan sebuah contoh. Katakanlah saya memiliki halaman sederhana ini dipecah menjadi tampilan yang ditentukan:

Perincian Halaman

Katakan HTML, setelah dirender, kira-kira seperti ini:

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

Semoga cukup jelas bagaimana HTML cocok dengan diagram.

The ParentViewmemegang 2 pandangan anak, InfoViewdan PhoneListViewjuga beberapa div tambahan, salah satunya #name, perlu diatur di beberapa titik. PhoneListViewmemiliki pandangan anak sendiri, sebuah array PhoneViewentri.

Demikian pertanyaan Anda yang sebenarnya. Saya menangani inisialisasi dan rendering berbeda berdasarkan tipe tampilan. Saya memecah pandangan saya menjadi dua jenis, Parentpandangan dan Childpandangan.

Perbedaan di antara mereka sederhana, Parentpandangan menahan pandangan anak sementara Childpandangan tidak. Jadi, dalam contoh saya, ParentViewdan PhoneListViewyang Parentdilihat, sementara InfoViewdan PhoneViewentri Childpandangan.

Seperti yang saya sebutkan sebelumnya, perbedaan terbesar antara kedua kategori ini adalah ketika mereka diizinkan untuk membuat. Di dunia yang sempurna, saya ingin Parentpandangan hanya ditampilkan sekali. Terserah pandangan anak mereka untuk menangani rendering ulang ketika model berubah. Childdi sisi lain, saya mengizinkan untuk merender ulang kapan pun mereka butuhkan karena mereka tidak memiliki pandangan lain yang mengandalkannya.

Secara lebih rinci, untuk Parenttampilan saya suka initializefungsi saya untuk melakukan beberapa hal:

  1. Inisialisasi pandangan saya sendiri
  2. Jadikan pandangan saya sendiri
  3. Buat dan inisialisasi pandangan anak apa saja.
  4. Tetapkan setiap anak untuk melihat elemen dalam tampilan saya (mis. Yang InfoViewakan ditugaskan #info).

Langkah 1 cukup jelas.

Langkah 2, rendering, dilakukan agar setiap elemen yang dilihat anak mengandalkan sudah ada sebelum saya mencoba untuk menetapkannya. Dengan melakukan ini, saya tahu semua anak eventsakan diatur dengan benar, dan saya dapat merender ulang blok mereka sebanyak yang saya inginkan tanpa khawatir harus mendelegasikan ulang apa pun. Saya tidak benar render- benar melihat anak di sini, saya membiarkan mereka melakukan itu di dalam mereka sendiri initialization.

Langkah 3 dan 4 sebenarnya ditangani pada saat yang sama ketika saya masuk elsambil membuat tampilan anak. Saya suka melewatkan elemen di sini karena saya merasa orang tua harus menentukan di mana dalam pandangannya sendiri anak diizinkan untuk meletakkan kontennya.

Untuk rendering, saya mencoba membuatnya tetap sederhana untuk Parentdilihat. Saya ingin renderfungsi tidak lebih dari membuat tampilan induk. Tidak ada delegasi acara, tidak ada rendering tampilan anak, tidak ada. Hanya membuat sederhana.

Kadang-kadang ini tidak selalu berhasil. Misalnya dalam contoh saya di atas, #nameelemen harus diperbarui setiap kali nama dalam model berubah. Namun, blok ini adalah bagian dari ParentViewtemplate dan tidak ditangani oleh tampilan khusus Child, jadi saya mengatasinya. Saya akan membuat semacam subRenderfungsi yang hanya menggantikan konten #nameelemen, dan tidak harus membuang seluruh #parentelemen. Ini mungkin tampak seperti peretasan, tapi saya benar-benar menemukan itu bekerja lebih baik daripada harus khawatir tentang rendering ulang seluruh DOM dan elemen-elemen pemasangan kembali dan semacamnya. Jika saya benar-benar ingin membuatnya bersih, saya akan membuat tampilan baru Child(mirip dengan InfoView) yang akan menangani #nameblok.

Sekarang untuk Childtampilan, initializationsangat mirip dengan Parenttampilan, hanya tanpa penciptaan Childtampilan lebih lanjut . Begitu:

  1. Inisialisasi pandangan saya
  2. Setup mengikat mendengarkan untuk setiap perubahan pada model yang saya pedulikan
  3. Jadikan pandangan saya

Childrendering tampilan juga sangat sederhana, cukup render dan atur konten saya el. Sekali lagi, jangan main-main dengan delegasi atau semacamnya.

Berikut ini beberapa contoh kode ParentViewtampilan saya :

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

Anda dapat melihat implementasi saya di subRendersini. Dengan memiliki perubahan yang terikat subRenderalih-alih render, saya tidak perlu khawatir tentang peledakan dan membangun kembali seluruh blok.

Berikut contoh kode untuk InfoViewblok:

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

Ikatan adalah bagian penting di sini. Dengan mengikat ke model saya, saya tidak perlu khawatir menyebut renderdiri saya secara manual . Jika model berubah, blok ini akan merender ulang sendiri tanpa memengaruhi tampilan lainnya.

The PhoneListViewakan mirip dengan ParentView, Anda hanya perlu logika sedikit lebih baik Anda initializationdan renderfungsi untuk menangani koleksi. Cara Anda menangani koleksi benar-benar terserah Anda, tetapi Anda setidaknya harus mendengarkan acara pengumpulan dan memutuskan bagaimana Anda ingin me-render (menambahkan / menghapus, atau hanya me-render ulang seluruh blok). Saya pribadi ingin menambahkan tampilan baru dan menghapus yang lama, bukan membuat ulang seluruh tampilan.

The PhoneViewakan hampir identik dengan InfoView, hanya mendengarkan perubahan model itu peduli.

Semoga ini sedikit membantu, tolong beri tahu saya jika ada sesuatu yang membingungkan atau tidak cukup detail.

Kevin Peel
sumber
8
Penjelasan benar-benar menyeluruh terima kasih. Pertanyaannya, saya pernah mendengar dan cenderung setuju bahwa memanggil renderke dalam initializemetode ini adalah praktik yang buruk, karena itu mencegah Anda menjadi lebih berkinerja dalam kasus-kasus di mana Anda tidak ingin langsung membuat. Apa yang Anda pikirkan tentang ini?
Ian Storm Taylor
Tentu. Hmm ... Saya hanya mencoba untuk memiliki pandangan yang diinisialisasi yang harus ditampilkan sekarang (atau dapat ditampilkan dalam waktu dekat, misalnya formulir edit yang disembunyikan sekarang tetapi ditampilkan saat mengklik tautan edit), sehingga harus segera ditampilkan. Jika mengatakan, saya memiliki kasus di mana tampilan perlu waktu untuk ditampilkan, saya pribadi tidak membuat tampilan itu sendiri sampai perlu ditampilkan, jadi tidak ada inisialisasi dan rendering yang akan berlangsung sampai diperlukan. Jika Anda memiliki contoh, saya bisa mencoba lebih mendetail tentang bagaimana saya menanganinya ...
Kevin Peel
5
Ini adalah batasan besar untuk tidak dapat merender ulang ParentView. Saya memiliki situasi di mana pada dasarnya saya memiliki kontrol tab, di mana setiap tab menunjukkan ParentView yang berbeda. Saya ingin merender ulang ParentView yang ada saat sebuah tab diklik untuk kedua kalinya, tetapi hal itu menyebabkan peristiwa di ChildViews hilang (saya membuat anak-anak di init, dan membuat mereka dalam render). Saya kira saya 1) menyembunyikan / menampilkan bukan render, 2) mendelegasikan acara di render ChildViews, atau 3) membuat anak-anak di render, atau 4) jangan khawatir tentang perf. sebelum masalah dan buat ulang ParentView setiap kali. Hmmmm ....
Paul Hoenecke
Saya harus mengatakan itu adalah batasan dalam kasus saya. Solusi ini akan berfungsi dengan baik jika Anda tidak mencoba merender ulang induknya :) Saya tidak memiliki jawaban yang baik tetapi saya memiliki pertanyaan yang sama dengan OP. Masih berharap menemukan cara 'Backbone' yang tepat. Pada titik ini, saya berpikir hide / show dan tidak membuat ulang adalah cara untuk saya.
Paul Hoenecke
1
bagaimana Anda meneruskan koleksi ke anak PhoneListView?
Tri Nguyen
6

Bagi saya sepertinya bukan ide terburuk di dunia untuk membedakan antara pengaturan intital dan pengaturan tampilan Anda selanjutnya melalui semacam flag. Untuk membuat ini bersih dan mudah bendera harus ditambahkan ke tampilan Anda sendiri yang harus memperpanjang tampilan Backbone (Base).

Sama seperti Derick Saya tidak sepenuhnya yakin apakah ini langsung menjawab pertanyaan Anda, tetapi saya pikir mungkin paling tidak layak disebutkan dalam konteks ini.

Lihat juga: Penggunaan Eventbus di Backbone

Jago berkelahi
sumber
3

Kevin Peel memberikan jawaban yang bagus - ini versi tl; dr saya:

initialize : function () {

    //parent init stuff

    this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!

    this.child = new Child();
},
Nate Barr
sumber
2

Saya mencoba untuk tidak menggabungkan antara tampilan seperti ini. Ada dua cara yang biasa saya lakukan:

Gunakan router

Pada dasarnya, Anda membiarkan fungsi router Anda menginisialisasi tampilan orang tua dan anak. Jadi pandangan tidak memiliki pengetahuan satu sama lain, tetapi router menangani semuanya.

Melewati el yang sama ke kedua tampilan

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

Keduanya memiliki pengetahuan tentang DOM yang sama, dan Anda dapat memesannya sesuai keinginan Anda.

Sơn Trần-Nguyễn
sumber
2

Apa yang saya lakukan adalah memberikan identitas kepada masing-masing anak (yang telah dilakukan Backbone untuk Anda: cid)

Ketika Container melakukan rendering, menggunakan 'cid' dan 'tagName' menghasilkan placeholder untuk setiap anak, jadi dalam templat anak-anak tidak tahu tentang di mana ia akan diletakkan oleh Container.

<tagName id='cid'></tagName>

daripada yang bisa Anda gunakan

Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element 
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();

tidak diperlukan placeholder yang ditentukan, dan Container hanya menghasilkan placeholder daripada struktur DOM anak-anak. Cotainer dan Anak-anak masih menghasilkan elemen DOM sendiri dan hanya sekali.

Vila
sumber
1

Berikut ini adalah mixin ringan untuk membuat dan merender subview, yang menurut saya mengatasi semua masalah di utas ini:

https://github.com/rotundasoftware/backbone.subviews

Pendekatan yang dilakukan oleh plug ini adalah membuat dan membuat subview setelah pertama kali tampilan induk diberikan. Kemudian, pada render tampilan induk yang berikutnya, $ .detach elemen subview, render kembali parent, lalu masukkan elemen subview di tempat yang sesuai dan render ulang. Dengan cara ini, subview objek digunakan kembali pada render berikutnya, dan tidak perlu mendelegasikan kembali peristiwa.

Perhatikan bahwa kasus tampilan koleksi (di mana masing-masing model dalam koleksi diwakili dengan satu subview) sangat berbeda dan pantas dibahas / solusi sendiri saya pikir. Solusi umum terbaik yang saya ketahui tentang hal ini adalah CollectionView di Marionette .

EDIT: Untuk kasing tampilan koleksi, Anda mungkin juga ingin melihat implementasi yang lebih berfokus pada UI ini , jika Anda memerlukan pilihan model berdasarkan klik dan / atau menyeret dan menjatuhkan untuk menyusun ulang.

Berani Dave
sumber