Як поводитися з ініціалізацією та рендерінгом підвидів у Backbone.js?


199

У мене є три різні способи ініціалізації та відображення перегляду та його підглядів, і кожен з них має різні проблеми. Мені цікаво дізнатись, чи є кращий спосіб вирішити всі проблеми:


Перший сценарій:

Ініціалізуйте дітей у функції ініціалізації батьків. Таким чином, не все застрягає в візуалізації, щоб менше блокувати при візуалізації.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Проблеми:

  • Найбільша проблема полягає в тому, що виклик візуалізації у батьків вдруге видалить усі прив'язки подій дитини. (Це пов'язано з тим, як $.html()працює jQuery .) Це можна пом'якшити, зателефонувавши this.child.delegateEvents().render().appendTo(this.$el);натомість, але тоді перший, і найчастіше це ви робите, більше зайвої роботи.

  • Додаючи дітей, ви змушуєте функцію візуалізації мати знання про структуру DOM батьків, щоб ви отримали потрібне впорядкування. Що означає, що для зміни шаблону може знадобитися оновлення функції візуалізації подання.


Сценарій другий:

Ініціалізуйте дітей у батьківському режимі initialize()ще, але замість того, setElement().delegateEvents()щоб додавати, використовуйте для встановлення дитини елемент у батьківському шаблоні.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Проблеми:

  • Це стає delegateEvents()необхідним зараз, що є незначним негативом, оскільки він необхідний лише при наступних дзвінках у першому сценарії.

Сценарій третій:

Ініціалізуйте дітей render()замість батьківського методу.

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

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

Проблеми:

  • Це означає, що функція візуалізації тепер повинна бути пов'язана також з усією логікою ініціалізації.

  • Якщо я відредагую стан одного з дочірніх переглядів, а потім зателефонуйте зробити візуалізацію для батьків, буде створено абсолютно нову дитину і весь її поточний стан буде втрачено. Що також здається, що це могло б отримати загрозу для витоку пам'яті.


Дійсно цікаво взяти на себе своїх хлопців. Який сценарій ви б використали? чи є четверта магічна, яка вирішує всі ці проблеми?

Ви коли-небудь відстежували відображений стан для перегляду? Скажи renderedBeforeпрапор? Здається, справді химерно.


1
Я, як правило, не переймаюсь посиланнями на дитячі погляди на батьківські погляди, тому що більша частина спілкування відбувається за допомогою моделей / колекцій та подій, викликаних їх змінами. Хоча 3-й випадок найближчий до того, що я використовую більшість часу. Випадок 1, де це має сенс. Крім того, більшу частину часу ви не повинні передавати весь погляд, а частину, яка змінилася
Том Ту,

Звичайно, я безумовно візую лише змінені деталі, коли це можливо, але навіть тоді я думаю, що функція візуалізації повинна бути доступною і не руйнувати в будь-якому разі. Як вам поводитися, якщо у батьків немає посилання на дітей?
Ян Шторм Тейлор

Зазвичай я слухаю події на моделях, пов’язаних з дитячими уявленнями - якщо я хочу зробити щось більш замовне, то я прив'язую події до підпрезентацій. Якщо потрібна така комунікація, я зазвичай створюю помічний метод для створення підпрезентацій, що пов'язує події тощо.
Том Ту,

1
Для більш високого рівня, пов'язане з обговоренням, см: stackoverflow.com/questions/10077185 / ... .
Бен Робертс

2
У другому сценарії, чому потрібно дзвонити delegateEvents()після setElement()? Згідно з документами: "... і перемістити делеговані події перегляду зі старого елемента до нового", сам setElementметод повинен обробляти повторне делегування подій.
рдамборський

Відповіді:


260

Це чудове питання. Хребна хребта чудова через відсутність її припущень, але це означає, що ви повинні (вирішити, як це зробити) реалізувати подібні речі самостійно. Переглянувши власні речі, я виявив, що я (вид) використовую поєднання сценарію 1 та сценарію 2. Я не думаю, що четвертий магічний сценарій існує, тому що, достатньо просто, все, що ви робите в сценарії 1 та 2, повинно бути зроблено.

Я думаю, що було б найпростіше пояснити, як мені подобається обробляти це прикладом. Скажіть, у мене ця проста сторінка розбита на вказані представлення:

Розбивка сторінки

Скажіть, HTML-код після рендерингу виглядає приблизно так:

<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>

Сподіваємось, досить очевидно, як HTML відповідає діаграмі.

ParentViewМає 2 дочірні уявлення, InfoViewі PhoneListView, а також кілька додаткових діви, один з яких, #name, повинен бути встановлений в будь - то момент. PhoneListViewмістить власні подання дитини, масив PhoneViewзаписів.

Отже, до вашого актуального питання. Я обробляю ініціалізацію та рендерінг по-різному, залежно від типу перегляду. Я розбиваю свої погляди на два типи, Parentпогляди та Childпогляди.

Різниця між ними проста: Parentперегляди зберігають уявлення дітей, тоді як Childпогляди не мають. Так у моєму прикладі ParentViewі PhoneListViewє Parentпогляди, а InfoViewі PhoneViewзаписи - це Childпогляди.

Як я вже згадував раніше, найбільша різниця між цими двома категоріями полягає в тому, коли їм дозволяється показувати. У ідеальному світі я хочу, щоб Parentпогляди відображалися лише один раз. Їх дочірнє уявлення залежить від будь-якого рендерінга, коли змінюються моделі. ChildЗ іншого боку, я дозволяю повторно відображати в будь-який час, коли їм потрібно, оскільки у них немає інших поглядів, на яких вони покладаються.

Трохи докладніше, для Parentпереглядів мені подобається, щоб мої initializeфункції виконували кілька речей:

  1. Ініціалізую власний погляд
  2. Надайте власний погляд
  3. Створіть та ініціалізуйте будь-які дитячі представлення даних.
  4. Призначте кожному дочірньому перегляду елемент в моєму вікні (наприклад InfoView, буде призначено #info).

Крок 1 досить зрозумілий.

Крок 2, візуалізація, робиться так, що будь-які елементи, на які спирається дитина, вже існують, перш ніж я спробую їх призначити. Роблячи це, я знаю, що вся дитина eventsбуде правильно встановлена, і я можу повторно відображати їх блоки стільки разів, скільки мені хочеться, не переживаючи про необхідність повторного делегування. Я фактично не бачу тут renderжодної дитини, я дозволяю їм це робити в межах власнихinitialization .

Етапи 3 та 4 фактично обробляються в той же час, коли я проходжу el під час створення дочірнього виду. Мені подобається передати тут елемент, оскільки я вважаю, що батько повинен визначити, куди, на власний погляд, дитині дозволяється розміщувати його вміст.

Що стосується візуалізації, я намагаюся тримати це досить просто для Parentперегляду. Я хочу, щоб ця renderфункція не мала нічого іншого, як відображення батьківського подання. Ні делегації подій, ні надання дитячих поглядів, нічого. Просто простий візуалізацію.

Іноді це не завжди працює. Наприклад, у моєму прикладі вище, #nameелемент потрібно буде оновлювати кожного разу, коли ім’я в моделі змінюється. Однак цей блок є частиною ParentViewшаблону і не обробляється виділеним Childвидом, тому я працюю над цим. Я створю яке - то subRenderфункція , яка тільки замінює вміст #nameелемента, а не громити весь #parentелемент. Це може здатися злому, але я дійсно виявив, що це працює краще, ніж потрібно турбуватися про повторне відтворення цілого DOM та повторне прив’язування елементів тощо. Якби я дійсно хотів зробити це чистим, я створив би новий Childвид (подібний до того InfoView), який би обробляв #nameблок.

Тепер для Childпереглядів, initializationце дуже схоже на Parentпогляди, тільки без створення будь-яких подальших Childпоглядів. Так:

  1. Ініціалізуйте мій погляд
  2. Налаштування пов'язує прослуховування будь-яких змін у моделі, яка мені цікава
  3. Надайте мені погляд

ChildВізуалізація перегляду також дуже проста, просто візуалізуйте та встановіть мій вміст el. Знову-таки, ніякого возитися з делегацією чи чогось подібного.

Ось приклад коду того, як ParentViewможе виглядати моє :

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);
    }
});

Ви можете побачити мою реалізацію subRenderтут. Зв'язавши зміни subRenderзамість render, я не повинен турбуватися про вибух і відновлення цілого блоку.

Ось приклад коду для InfoViewблоку:

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());
    }
});

Тут є важлива частина палітур. Прив’язуючись до своєї моделі, мені ніколи не потрібно турбуватися про те, щоб вручну дзвонити renderсобі. Якщо модель зміниться, цей блок повторно відобразиться, не впливаючи на інші види.

PhoneListViewБуде схожий на ParentView, вам просто потрібно трохи більше логіки в обох ваших initializationі renderфункцій в колекції ручок. Як ви обробляєте колекцію, насправді залежить від вас, але вам потрібно, принаймні, слухати події колекції та вирішувати, як ви хочете візуалізувати (додати / вилучити або просто повторно відредагувати весь блок). Мені особисто подобається додавати нові перегляди та видаляти старі, а не повторювати весь перегляд.

PhoneViewБуде майже ідентичний InfoView, тільки прислухаючись до моделі змін , які вона піклується про.

Сподіваємось, це трохи допомогло, будь ласка, повідомте мене, якщо щось є заплутаним чи недостатньо детальним.


8
Дійсно ґрунтовне пояснення дякую. Питання, однак, я чув і схильний погоджуватися, що виклик renderвсередині initializeметоду є поганою практикою, тому що він заважає вам бути більш ефективним у випадках, коли ви не хочете одразу робити рендерінг. Що ти думаєш про це?
Ян Шторм Тейлор

Звичайно. Хм ... Я намагаюся лише ініціалізувати представлення, яке повинно бути показане зараз (або може бути показане найближчим часом, наприклад, форма редагування, яка прихована зараз, але показана при натисканні посилання на редагування), тому їх слід одразу ж винести. Якщо скажіть, у мене є випадок, коли для перегляду потрібен певний час, я особисто не створюю сам перегляд, поки його не потрібно буде показати, тому ініціалізація та візуалізація не відбуватиметься до необхідності. Якщо у вас є приклад, я можу спробувати детальніше розібратися в тому, як я впораюся з цим ...
Кевін Піл

5
Велике обмеження - не мати змоги повторно виводити ParentView. У мене є ситуація, коли в основному я маю керування вкладками, де кожна вкладка показує інший ParentView. Я хотів би просто відновити існуючий ParentView, коли вдруге клацніть вкладку, але це робить причиною втрати подій у ChildViews (я створюю дітей у програмі init та відтворюю їх у візуалізації). Я думаю, що я або 1) сховати / показати замість візуалізації, 2) delegateEvents у візуалізації ChildViews, або 3) створити дітей у візуалізації, або 4) не турбуватися про perf. перш ніж виникнути проблеми і щоразу створювати ParentView.
Хмммм

Я повинен сказати, що це обмеження в моєму випадку. Це рішення спрацює чудово, якщо ви не спробуєте повернути батька :) Я не маю гарної відповіді, але у мене те саме питання, що і в ОП. Все ще сподіваюся знайти належний спосіб "Хребта". На даний момент я думаю про те, щоб приховати / показати і не повторювати - це шлях для мене.
Пол Хонекке

1
як ви передаєте колекцію дитині PhoneListView?
Tri Nguyen

6

Я не впевнений, чи це прямо відповідає на ваше запитання, але я думаю, що це актуально:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

Контекст, в якому я створив цю статтю, звичайно, різний, але я думаю, що два запропоновані нами рішення, разом із плюсами та мінусами кожного, повинні змусити вас рухатись у правильному напрямку.


6

Мені здається, не найгірша ідея у світі розрізняти інтитальну установку та наступні налаштування ваших поглядів за допомогою якогось прапора. Щоб зробити це чистим і легким, прапор слід додати до власного представлення, яке повинно розширити вигляд магістральної (базової).

Як і Derick, я не зовсім впевнений, чи це прямо відповідає на ваше запитання, але я думаю, що це, принаймні, варто згадати в цьому контексті.

Також дивіться: Використання шини подій у хребті


3

Кевін Піл дає чудову відповідь - ось моя версія tl; dr:

initialize : function () {

    //parent init stuff

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

    this.child = new Child();
},

2

Я намагаюся уникати зв'язку між подібними поглядами. Зазвичай я роблю два способи:

Використовуйте роутер

В основному, ви дозволяєте функції маршрутизатора ініціалізувати подання батьків і дітей. Таким чином, погляд не знає один одного, але маршрутизатор обробляє все це.

Передаючи однаковий ель на обидва погляди

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

Обидва мають знання одного і того ж DOM, і ви можете замовити їх у будь-якому місці.


2

Те, що я роблю, - це надання дітям ідентичності (що Хребет уже зробив це для вас: cid)

Коли Container робить візуалізацію, використовуючи 'cid' та 'tagName', генерує заповнювач для кожної дитини, тому у шаблоні діти не мають уявлення про те, куди він буде розміщений контейнером.

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

ніж ви можете використовувати

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();

не потрібний вказаний заповнювач, а контейнер генерує лише заповнювач, а не структуру DOM для дітей. Cotainer та Children все ще генерують власні елементи DOM і лише один раз.


1

Ось невеликий міксин для створення та візуалізації підвидів, який, на мою думку, вирішує всі проблеми в цій темі:

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

Підхід, що використовується цим плагіном, полягає у створенні та рендерінгах підпрезентації після першого відображення батьківського подання. Потім, на наступних візуалізаціях батьківського подання, $ .detach елементи підпогляду, повторно рендеринга батьків, а потім вставити елементи підпорядкування у відповідні місця та повторно надати їх. Таким чином об'єкти підперегляду повторно використовуються при наступних візуалізаціях, і немає необхідності повторно делегувати події.

Зауважимо, що вигляд перегляду колекції (де кожна модель у колекції представлений одним підвидом) зовсім інший і заслуговує на власне обговорення / рішення, на мою думку. Найкраще загальне рішення, яке мені відомо в цьому випадку, є CollectionView в Маріонетті .

РЕДАКТУВАННЯ: Для випадку перегляду колекції ви також можете перевірити цю більш орієнтовану на користувача інтерфейс , якщо вам потрібен вибір моделей на основі клацань та / або перетягування та падіння для переупорядкування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.