» » » Управление состоянием в Polymer 2.0. За пределами parent/child биндингов

 

Управление состоянием в Polymer 2.0. За пределами parent/child биндингов

Автор: admin от 29-12-2017, 21:40, посмотрело: 36

Организуем общее состояние между разделенными DOM-элементами без Redux



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







Options



Send Notifications






[/code]

Нам хотелось бы сделать свойство options доступным из другого места так, что если кому-нибудь нужен доступ к его дочернему свойству subscribe(или любому другому, которое мы добавим), этот кто-то смог бы получить его и оно обновлялось бы при любых изменениях, но нам не хотелось бы все было открыто — мы хотим контролировать доступ к нему.



Конечно, мы могли бы начать использовать что-то вроде Redux, и хотя это действительно вариант, такое решение имеет значительную стоимость в виде дополнительных требований к сборке и усложнения кода. У этого варианта есть неоспоримые плюсы, и если есть смысл использовать его в вашем приложении по другим причинам, это может быть хорошим выбором, но также может быть и оверкиллом и привнести столько же проблем, сколько решает, особенно будучи добавленным к библиотеке, которая изначально не создавалась под такой дизайн.



Одна из причин полюбить Polymer это то, что он построен на возможностях платформы и мы увидим, чего можно добиться просто используя их + немного чистого javascript-а.



Сейчас подходящий момент, чтобы понять IIFE, или немедленно-вызываемые функции
, которые дают нам возможность запускать код и объявлять переменные не выставляя их наружу. Это базовая структура, оборачивающая объявление нашего класса — она объявляет функцию и немедленно выполняет ее(в точности так, как и следует из ее названия):



(function() {
  // existing code
}());


Это фактически не меняет ничего, кроме того что скрывает наш класс MyOptions от внешнего мира(это не важно, так как единственная важная для нас вещь это то, что он вызывает window.customElements.define).



В нашем приложении будет лишь один экземпляр MyOptions, а другим элементам понадобится доступ к нему, поэтому добавим переменную, ссылающуюся на него и установим ее корректное значение в конструкторе, когда элемент будет создан:



let optionsInstance = null;

// in class definition:
constructor() {
  super();
  if (!optionsInstance) optionsInstance = this;
} 


optionsInstance все еще скрыт внутри нашей IIFE, но теперь чтобы мы в него не положили, оно будет иметь доступ к инициализированному экземпляру MyOptions.



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



// in properties:
subscribers: {
  type: Array,
  value: () => []
}

// in class definition:
register(subscriber) {
  this.subscribers.push(subscriber);
  subscriber.options = this.options;
  subscriber.notifyPath('options');
}

unregister(subscriber) {
  var i = this.subscribers.indexOf(subscriber);
  if (i > -1) this.subscribers.splice(i, 1)
}


Обратите внимание, что когда подписчик регистрируется, мы добавляем его в список, а также инициализируем в нем локальную переменную, указывающую на объект options. Здесь мы также сталкиваемся с детекцией изменений Polymer-а — установка свойства сама по себе не уведомляет подписчика о том, что это произошло, поэтому нам нужен вызов notifyPath. Также мы хотим уведомить всех подписчиков всякий раз когда какие-либо свойства объекта options изменяются(например, если ‘subscribe’ был вызван, а не только когда ссылка на объект изменяется) и для этого мы используем observer со звездочкой, чтобы сказать что нас интересуют “все изменения”:



static get observers() {
  return [
    'optionsChanged(options.*)'
  ]
}

optionsChanged(change) {
  for(var i = 0; i < this.subscribers.length; i++) {
    this.subscribers[i].notifyPath(change.path);
  }
}


Часть, связанная с уведомлениями проста — независимо от того, по какому пути произошло изменение у нашего observer-а, это тот же самый путь, об изменении в котором мы должны уведомить наших подписчиков, поэтому мы просто проходимся по ним циклом и вызываем notifyPath для каждого.



Теперь у нас есть хуки и нотификации, которые нужны нам для подписчиков, и у нас есть два варианта. Создадим элемент доступа(accessor element), который будет внутри той же самой IIFE(а значит он будет иметь доступ к optionsInstance):



class MyOptionsValue extends Polymer.Element {
  static get is() { return 'my-options-value'; }

  static get properties() {
    return {
      options: {
        type: Object,
        notify: true
      }
    }
  }

  connectedCallback() {
    super.connectedCallback();
    optionsInstance.register(this);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    optionsInstance.unregister(this);
  }
}

window.customElements.define(MyOptionsValue.is, MyOptionsValue);


сonnected- и disconnected-коллбеки прекрасно подходят для регистрации и отмены регистрации экземпляров. Это означает, что элементы, которые могут находиться очень далеко друг от друга в DOM-дереве, могут иметь прямые ссылки друг на друга и тем самым избегать цепочки property-биндингов, если мы ограничены использованием DOM-структурой для коммуникации.



Экземпляр может использоваться внутри элемента, будучи импортированным в него:



<link rel="import" href="my-options.html">


и установив с ним связь посредством биндинга:



<my-options-value options="{{ options }}"></my-options-value>
<p>Send notifications option is: [b][[ options.subscribe ]][/b]</p>


Нам нужно выставить notify: true в декларации свойств элемента из-за двунаправленного биндинга(child-to-parent), обозначенного фигурными скобками. Экземпляр MyOptions сообщает экземпляру(или экземплярам) MyOptionsValue об изменении, и им, в свою очередь, нужно уведомить об этом элемент, в котором они находятся.



Это работает, и мы можем включить или выключить чекбокс и наблюдать обновления, но у нас есть дополнительный элемент, дополнительный биндинг, и мы должны добавлять свойство options в каждый view-элемент, если мы хотим видеть предупреждения от линтера про неопределенные свойства:



class MyView extends Polymer.Element {
  static get is() { return 'my-view'; }

  static get properties() {
    return {
      options: {
        type: Object
      }
    }
  }
}


ох, еще одно свойство ‘options’...



Один из способов чуть упростить ситуацию — использовать миксин. Миксин похож на наследование классов и дает возможность комбинировать определения элементов, таким образом код можно переиспользовать вместо дублирования(ранее, в Polymer 1.0, миксины были известны как behaviors).



Вместо создания элемента доступа в нашем view-элементе и привязывания к свойству options, которое он предоставляет, наш view-элемент сам становится элементом доступа — он имеет собственное свойство options и обрабатывает регистрацию и отмену регистрации самого себя без дополнительного кода, не считая добавления миксина в определение класса:



class MyView extends MyOptionsMixin(Polymer.Element) {
  static get is() { return 'my-view'; }
}


Нам все еще нужно импортировать my-options.html, но наш view-элемент стал проще и не требует промежуточного элемента доступа:



<p>Send notifications option is: [b][[ options.subscribe ]][/b]</p>


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



У данного подхода на самом деле есть название, он называется “mono-state” паттерн. Есть уже существующие элементы, вроде iron-meta, обеспечивающие общий подход, но на мой взгляд проще, чище и быстрее создавать специфичные для конкретного приложения реализации — часто они проще в адаптации для специфических случаев и кажутся более понятными, чем использование промежуточных компонентов.



Вот финальный, полный код для наших классов, который, я надеюсь, выглядит проще. Мне стоит также извиниться за использование “subscribe” в качестве имени, это можно перепутать с подписками экземпляра. Изначально я использовал название “notify”, что было еще хуже(так как это имя одного из свойств Polymer):



<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/paper-checkbox/paper-checkbox.html">

<dom-module id="my-options">
  <template>
    <style>
      :host {
        display: block;
        padding: 16px;
      }
      h3, p {
        margin: 8px 0;
      }
    </style>
    <h3>Options</h3>
    <p>
      <paper-checkbox checked="{{ options.subscribe }}">Send Notifications</paper-checkbox>
    </p>
  </template>

  <script>
    (function() {

      let optionsInstance = null;

      class MyOptions extends Polymer.Element {
        static get is() { return 'my-options'; }

        static get properties() {
          return {
            options: {
              type: Object,
              value: () => ({
                subscribe: false
              })
            },
            subscribers: {
              type: Array,
              value: () => []
            }
          }
        }

        static get observers() {
          return [
            'optionsChanged(options.*)'
          ]
        }

        constructor() {
          super();

          if (!optionsInstance) optionsInstance = this;
        }

        register(subscriber) {
          this.subscribers.push(subscriber);
          subscriber.options = this.options;
          subscriber.notifyPath('options');
        }

        unregister(subscriber) {
          var i = this.subscribers.indexOf(subscriber);
          if (i > -1) this.subscribers.splice(i, 1)
        }

        optionsChanged(change) {
          for(var i = 0; i < this.subscribers.length; i++) {
            this.subscribers[i].notifyPath(change.path);
          }
        }
      }

      window.customElements.define(MyOptions.is, MyOptions);

      MyOptionsMixin = (superClass) => {
        return class extends superClass {
          static get properties() {
            return {
              options: {
                type: Object
              }
            }
          }

          connectedCallback() {
            super.connectedCallback();
            optionsInstance.register(this);
          }

          disconnectedCallback() {
            super.disconnectedCallback();
            optionsInstance.unregister(this);
          }
        }
      }
    }());
  </script>
</dom-module>


View-элемент, потребитель:



<link rel="import" href="../bower_components/polymer/polymer-element.html">

<link rel="import" href="my-options.html">
<link rel="import" href="shared-styles.html">

<dom-module id="my-view2">
  <template>
    <style include="shared-styles">
      :host {
        display: block;
        padding: 10px;
      }
    </style>

    <div class="card">
      <div class="circle">2</div>
      <h1>View Two</h1>
      <p>Ea duis bonorum nec, falli paulo aliquid ei eum.</p>
      <p>Id nam odio natum malorum, tibique copiosae expetenda mel ea.Detracto suavitate repudiandae no eum. Id adhuc minim soluta nam.Id nam odio natum malorum, tibique copiosae expetenda mel ea.</p>

      <p>Send notifications option is: [b][[ options.subscribe ]][/b]</p>
    </div>
  </template>

  <script>
    class MyView2 extends MyOptionsMixin(Polymer.Element) {
      static get is() { return 'my-view2'; }
    }

    window.customElements.define(MyView2.is, MyView2);
  </script>
</dom-module>


Обратите внимание: пример из этого поста работает потому, что UI панели ‘Options’ всегда первый по счету в DOM, поэтому подписчики элемента доступа всегда могут найти существующий экземпляр. Если это не так, то достаточно несложно использовать вместо этого функцию, чтобы первый вызывающий создавал единственный экземпляр — взгляните на iron-a11y-announcer, в котором это реализовано.



Кроме того, на тот случай, если это недостаточно понятно, хотя MyOptionsMixin определен внутри IIFE, на самом деле находится в области видимости window, поэтому другие элементы за пределами IIFE могут видеть и использовать его(если бы мы написали var MyOptionsMixin…, тогда это бы не работало, он был бы видим только внутри IIFE). Мне следовало использовать window.MyOptionsMixin чтобы сделать это понятнее, либо, как более распространено, использовать глобальное пространство имен(дочерний объект в window) также, как это делает сам Polymer. У вас оно уже может быть — они полезны для хранения конфигурационных свойств. Безопасный способ проверить и добавить что-либо в него выглядит примерно так:



MyApp = windows.MyApp || { }
MyApp.MyOptionsMixin = ...


(после чего вы всегда можете использовать MyApp.MyOptionsMixin, ссылаясь на него).

Источник: Хабрахабр

Категория: Информационная безопасность » Криптография

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *