Реактивные свойства контрола

Функционал реактивности на свойства для прикладного разработчика означает, что изменение свойств контрола будет вести к его перерисовке.

Свойство контрола становится реактивным, когда используется в шаблоне. Если значение свойства изменяется, Wasaby автоматически запрашивает его перерисовку в шаблоне.

Для свойств с типом Object перерисовка происходит, когда выполнено одно из следующих условий:

  • свойство меняется по ссылке
  • является экземпляром наследника класса модели Types/entity:Model и модель изменяется.
  • является экземпляром наследника класса рекорда Types/entity:Record и рекорд изменяется.
  • является экземпляром наследника класса реактивного объекта Types/entity:applied.ReactiveObject и объект изменяется.

Для свойств с типом Array к автоматической перерисовке приводят изменения, вызванные методами push, pop, shift, unshift, splice, sort и reverse. Если удалять из массива через delete или менять значение через индекс, автоматического запроса на перерисовку не произойдет. Также, перерисовка будет выполнена, если свойство типа Array будет изменено по ссылке.

Пример реактивных свойств контрола:

<!-- Template.wml -->
<div>
  <Button on:click="clickHandler()"/>
  <Label text="{{iterator}}" />
</div>
// Module.js
define("Parent/Module", ["UI/Base", "wml!Parent/Template"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      iterator: 0,
      clickHandler: function() {
         this.iterator++;
      }
   });
   return ModuleClass;
});

В данном примере при нажатии на кнопку свойство iterator меняется. Так как это свойство используется в шаблоне, автоматически запустится перерисовка и Label выведет новое значение.

Возможные проблемы

Далее описаны проблемы, с которыми могут встретиться прикладные разработчики при работе с реактивными свойствами.

Замена свойства на обработчик события

В обработчике клика для свойства this._passwordVisible устанавливается значение true, но в шаблоне контрола это свойство не используется. Обращение к свойству происходит только в getter'е:

isVisiblePassword: function () {
   return this._passwordVisible;
}

Если раньше на обработчик события позвался бы _forceUpdate(), и при построении верстки позвался бы isVisiblePassword() и вернул новое значение, то сейчас этого не произойдет и ничего не перерисуется.

Решение

Есть несколько способов решить проблему:

  • после изменения свойства вызвать в обработчике вручную _forceUpdate();
  • использовать свойство _passwordVisible напрямую в шаблоне;
  • в некоторых случаях, удобно передавать свойство аргументом в функцию, используемую в шаблоне, продекларировав таким образом свойства, которые вы хотите сделать реактивными.

Подписка на нативные события браузера

Разработчик подписался на нативные события браузера, такие как resize, и надеется на то, что наличие подписки приведет к перерисовке, а также выполнится код в _beforeUpdate (или _afterUpdate). Сейчас этого не произойдет.

Решение

Логика, которую требуется выполнить на событие, должна находиться в обработчике события.

Поведение массивов при их изменении через вызов метода, а не по ссылке

Рассмотрим случай, когда в реактивном свойстве хранится массив, и значение этого свойства используется в шаблоне контрола.

При изменении массива через нативные методы происходит перерисовка контрола, которому принадлежит такое реактивное свойство. В следующем примере показан вызов метода push() для реактивного свойства array.

// Запустит перерисовку контрола.
control.array.push(123);

Однако, если массив передается опцией в дочерний контрол, то при изменении массива дочерний контрол перерисовываться не будет.

<ChildControl name="child" childArray="{{array}}"/>
// Не запустит перерисовку дочернего контрола.
this._children.child.childArray.push(123);

Чтобы перерисовать дочерний контрол, проверяется изменение значения опции "по ссылке".

Решение

Для решения заявленной проблемы необходимо создать копию массива.

// Изменяем значение реактивного свойства.
control.array.push(123);
// Создаём копию массива.
control.array = this.array.slice();