Следуйте за мной в Twitter, с радостью приму ваши предложения по темам или улучшениям / Крис

NGRX - это реализация паттерна Redux. Шаблон Redux сам по себе является шаблоном публикации / подписки, также называемым Pub-Sub. По сути, этот шаблон связан с одним ключевым моментом: когда происходит изменение, это может быть проблемой для одной или нескольких частей вашего приложения, и вам нужен способ передать это изменение. Вам также нужен способ сделать это без привязки вашего кода

Хорошо, давайте возьмем это с самого начала и изучим Pub Sub, Redux, необходимый нам Typescript и некоторый базовый RxJS, больше не будем путаться, хорошо?

Эта статья является частью серии:

В этой статье рассматривается следующее:

  • Pub-Sub, что это такое и зачем он нам нужен
  • Взаимодействие с сообщениями, реализация шаблона Pub-Sub и то, как мы можем применить это к любой компонентной библиотеке / фреймворку, которые мы используем.

Как мы уже говорили в начале, NGRX - это реализация Redux, и что мы действительно добавляем в наше приложение, так это возможность делать Pub-Sub, чтобы транслировать сообщение при изменении в системе. Хорошо, а когда это произойдет тогда? Что ж, представьте, что вы настроили свое приложение на определенный язык, а затем изменили этот язык, что должно произойти? Что ж, скорее всего, определенный языковой набор означает, что ваше приложение частично или полностью перевело приложение на этот выбранный язык. Итак, когда вводится язык, необходимо будет выполнить некоторый перевод, вопрос в том, какая часть вашего приложения будет затронута? Вы, наверное, знаете это лучше, чем я, но давайте попробуем привести некоторые аргументы. Ваше приложение, вероятно, разделено на несколько компонентов, чтобы упростить обслуживание. Если ваше приложение состоит из нескольких страниц, на каждой из которых есть по одному компоненту, вам не нужен Pub-Sub.

А?

Если у вас есть простое приложение, скажем, с 5 страницами и одним компонентом на странице, тогда, когда вы переходите на новый язык, вам нужно только повлиять на страницу, на которой вы находитесь. В этот момент вы, вероятно, звоните в службу, которая запрашивает эти новые переводы с помощью псевдокода, например:

function onLanguageChange(newLang) {
  service.getTranslationsByLang(newLang); 
}

Теперь вам нужно найти способ использовать эти новые переводы и применить их на своем сайте следующим образом:

function onLanguageChange(newLang) { 
  const translations = service.getTranslationsByLang(newLang);
  this.title = translations.title; 
  this.description = translations.description 
}

Затем, когда вы переходите с этой страницы на другую, вам нужно сделать что-то подобное, как указано выше, но как можно раньше, в конструкторе или в ngOnInit, например:

constructor(){ 
  const translations = service.getTranslationsByLang(newLang);
  this.title = translations.title; 
  this.description = translations.description; 
}

Итак, когда мне нужен Pub / Sub?

Как только у вас будет много компонентов на странице и вы сделаете изменение в масштабе приложения, например, изменение языка, вам нужно сообщить всем этим компонентам, что изменение произошло, о чем вам нужно позаботиться о. Теперь, если вы используете Angular, вы можете просто установить свойство для этих компонентов, используя их @Input привязку, но это быстро превращается в очень запутанный беспорядок, позвольте мне показать вам:

class Component { 
  private _lang = ''; 
  @Input('language') 
  set lang(value) { 
    this._lang = value; 
    const translations = service.getTranslationsByLang(newLang);
    this.title = translations.title; 
    this.description = translations.description; 
  }; 
  get language() { 
    return this._lang; 
  } 
}

Чувствуешь себя немного связанным, и нужно много писать?

Общение с сообщениями

На данный момент боль реальна, поскольку вы, скорее всего, написали приведенный выше код, возможно, для 10 различных компонентов. Возможно, вы придумали хороший способ опустить вызов service.getTranslationsByLang и сделать это только в одном центральном месте, а затем мы отправим только переводы.

В чем же состоит боль? Ах, теперь мы задаем правильный вопрос, и вред - НЕТ, потому что нам нужно написать 3–4 строки и назначить правильный перевод каждому свойству в компоненте - ущерб в том, что мы используем свойство для установки языка в X количество компонентов. Такой способ передачи этой информации очень взаимосвязан и также очень зависит от конкретной структуры, которую мы используем.

Хорошо, а что лучше сделать?

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

  • Использование ванильной реализации
  • Использование библиотеки EventEmitter
  • Использование RxJS

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

Ванильная реализация

В реализации, не использующей библиотеки, нам необходимо выполнить следующее:

  • нам нужен способ отправить сообщение
  • способ подписаться, отписаться от сообщений

Хорошо, давайте посмотрим на наивную реализацию:

class PubSub { 
  constructor() { 
    this.listeners = []; 
  } 
  
  send(messageType, message) { 
    this.listeners.forEach(l => l(messageType, message)); 
  } 
  subscribe(listener) { 
    this.listeners.push(listener); 
  } 
  unsubscribe(list) { 
    this.listeners = this.listeners.filter( l => l !== list); 
  } 
} 
module.exports = PubSub;

Наша реализация выше поддерживает отправку сообщения с использованием метода send(), а методы subscribe() и unsubscribe() дают нам возможность добавлять / удалять слушателей. Это почти все, что нам нужно для работы с Pub-Sub.

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

const PubSub = require('./pubsub'); 
const ps = new PubSub(); 
const l1 = (type, message) => { 
  console.log('sub1'); 
  console.log(`Type: ${type}, Message: ${message} `); 
}; 
const l2 = (type, message) => { 
  console.log('sub2'); 
  console.log(`Type: ${type}, Message: ${message} `); 
} 
ps.subscribe(l1); 
ps.subscribe(l2) 
ps.send('INCREMENT', 1); 
ps.send('language', 'en'); 
// l1 is no longer listening
ps.unsubscribe(l1); 
ps.send('spam', 'hello');

Выше мы видим, что мы определяем два слушателя l1 и l2. Оба слушателя подписываются на наш PubSub класс. Затем мы видим, что l1 перестает слушать, вызывая unsubscribe(). Запустив программу, мы получаем следующий вывод:

Выходные данные показывают, как оба наших слушателя получают сообщение INCREMENT и language, тогда как только l2 получает сообщение spam, поскольку l1 отписались до того, как это сообщение могло появиться.

Хорошо, наш PubSub класс вроде работает. Так какой во всем этом смысл? Идея состоит в том, чтобы показать две вещи:

  1. общение с сообщениями
  2. общение слабо связанно, т.е. мы не полагаемся на детали реализации Angular, Vue, React или любой другой библиотеки, которую мы используем.

Применив это к нашим компонентам, где мы меняем язык, мы получим следующий код:

class PubSub { 
  constructor() { 
    this.listeners = []; 
  } 
  send(messageType, message) { 
    this.listeners.forEach(l => l(messageType, message)); 
  } 
  subscribe(listener) { 
    this.listeners.push(listener); 
  } 
  unsubscribe(list) { 
    this.listeners = this.listeners.filter( l => l !== list); 
  } 
} 
const pubSub = new PubSub(); module.exports = pubsub;

Выше мы немного изменили наш PubSub класс, создав экземпляр PubSub, и именно этот экземпляр мы экспортируем.

import PubSub = require('pubsub'); 
class Component {   
  constructor() { } 
  setLanguage() { 
    pubsub.send('changeLanguage', 'en'); 
  } 
}

Компонент отправки выше просто импортирует наш экземпляр PubSub и отправляет сообщение, вызывая метод send().

import PubSub = require('pubsub'); 
class OtherComponent { 
  constructor() {  
    pubsub.subscribe(this.onMessage).bind(this); 
  } 
  onMessage(type, message) { 
    if (type === 'changeLanguage') { 
      const translations = service.getTranslationsByLang(message);
      this.title = translations.title; 
    } 
  } 
}

Компонент прослушивания использует тот же экземпляр PubSub и устанавливает свой метод onMessage() в качестве прослушивателя в конструкторе. Затем этот же метод onMessage() вызывается при отправке сообщения. Стоит также отметить, как мы проверяем, что отправляемое сообщение имеет тип changeLanguage, прежде чем мы продолжим и получим новые переводы из службы переводов.

Резюме

В статье рассказывается об основном шаблоне как для NGRX, так и для Redux, а именно о Pub-Sub. Возможность отправить сообщение одному или нескольким слушателям. Мы обсудили, когда нам это нужно, и даже создали очень простую реализацию, класс с именем PubSub, и показали, как мы можем использовать его с нашими компонентами.

В следующей части мы специально рассмотрим шаблон Redux, который является более специализированной версией Pub-Sub, по сути, тот же шаблон, но с памятью нашего текущего состояния и более защищенным способом изменения состояния.