Работа с nested моделями

В Backbone.js nested модели (вложенные модели) позволяют структурировать данные, создавая иерархию объектов, где одна модель может содержать другие модели или коллекции. Это особенно актуально при работе с сложными JSON-структурами, получаемыми от сервера. Прямой поддержки вложенных моделей в Backbone нет, поэтому требуется ручная организация и расширение стандартных возможностей фреймворка.


Основы nested моделей

В Backbone каждая модель хранит данные в объекте attributes. При стандартном определении модели все поля являются примитивами или объектами JavaScript. Для работы с вложенными моделями необходимо:

  1. Создать отдельную Backbone-модель для вложенного объекта.
  2. Переопределить методы parse, toJSON и иногда set, чтобы правильно обрабатывать вложенные структуры.

Пример базовой вложенной модели:

var Address = Backbone.Model.extend({
  defaults: {
    street: '',
    city: '',
    zipcode: ''
  }
});

var User = Backbone.Model.extend({
  defaults: function() {
    return {
      name: '',
      email: '',
      address: new Address()
    };
  },

  parse: function(response) {
    if (response.address) {
      this.get('address').set(response.address);
      delete response.address;
    }
    return response;
  },

  toJSON: function() {
    var json = _.clone(this.attributes);
    json.address = this.get('address').toJSON();
    return json;
  }
});

В этом примере User содержит вложенную модель Address. Метод parse обрабатывает данные от сервера, а toJSON обеспечивает корректную сериализацию для отправки обратно.


Методы управления вложенными моделями

1. Инициализация через конструктор

Nested модель можно передавать при создании родительской модели:

var user = new User({
  name: 'Иван',
  email: 'ivan@example.com',
  address: new Address({ street: 'Ленина', city: 'Москва', zipcode: '101000' })
});

2. Обновление данных вложенной модели

user.get('address').set({ city: 'Санкт-Петербург' });

Важно помнить, что set на родительской модели для вложенного объекта без правильного переопределения set не обновит сам объект Address, а заменит его обычным объектом.

3. Автоматическое создание вложенных моделей

Можно переопределить set, чтобы автоматически конвертировать обычные объекты в вложенные модели:

set: function(key, val, options) {
  var attrs = key;
  if (key === Object(key) && !(key instanceof Backbone.Model)) {
    attrs = key;
  } else {
    attrs = {};
    attrs[key] = val;
  }

  if (attrs.address && !(attrs.address instanceof Address)) {
    attrs.address = new Address(attrs.address);
  }

  return Backbone.Model.prototype.set.call(this, attrs, options);
}

Это гарантирует, что любое присваивание обычного объекта address будет преобразовано в модель Address.


Nested коллекции

Nested структуры могут включать не только модели, но и коллекции. Пример:

var Task = Backbone.Model.extend({
  defaults: { title: '', done: false }
});

var TaskList = Backbone.Collection.extend({
  model: Task
});

var Project = Backbone.Model.extend({
  defaults: function() {
    return {
      name: '',
      tasks: new TaskList()
    };
  },

  parse: function(response) {
    if (response.tasks) {
      this.get('tasks').reset(response.tasks);
      delete response.tasks;
    }
    return response;
  },

  toJSON: function() {
    var json = _.clone(this.attributes);
    json.tasks = this.get('tasks').toJSON();
    return json;
  }
});

Использование:

var project = new Project({
  name: 'Backbone Tutorial',
  tasks: [
    { title: 'Изучить модели', done: true },
    { title: 'Изучить коллекции', done: false }
  ]
});

Методы reset, add, remove коллекции позволяют управлять элементами динамически, сохраняя структуру данных.


Подписка на события вложенных моделей

Для корректного реагирования на изменения вложенных данных важно использовать слушатели событий:

var user = new User({
  name: 'Анна',
  address: new Address({ street: 'Пушкина', city: 'Казань', zipcode: '420000' })
});

user.get('address').on('change', function() {
  console.log('Адрес изменён:', this.toJSON());
});

user.get('address').set({ city: 'Нижний Новгород' });

При этом изменение вложенной модели вызывает событие только на самой модели, а не на родительской. Если нужно, чтобы родительская модель также реагировала, создаются прокси-обработчики:

initialize: function() {
  this.listenTo(this.get('address'), 'change', this.onAddressChange);
},

onAddressChange: function() {
  this.trigger('change:address', this, this.get('address'));
}

Практические рекомендации

  • Всегда использовать parse для корректного преобразования данных с сервера.
  • Переопределять toJSON для сохранения вложенной структуры при отправке на сервер.
  • Для коллекций использовать reset при инициализации и загрузке данных.
  • Применять listenTo вместо прямого on для удобного управления жизненным циклом событий.
  • Избегать прямой замены вложенной модели обычным объектом — это ломает структуру и обработку событий.

Nested модели делают Backbone мощным инструментом для управления сложными клиентскими данными, позволяя сохранять иерархическую структуру и обеспечивать реактивное обновление интерфейса.