Некоторые вещи, которые мне хотелось бы знать, прежде чем я начну работать с Angular: NgRx

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

Как только мы начнем выходить за рамки написания приложения hello world на angular. Мы начинаем сталкиваться с серьезной проблемой, как нам управлять состоянием в нашем приложении.

Есть две проблемы с состоянием в приложении angular: сначала оно должно быть выражено (записано) с помощью объектов (которые становятся нашей моделью данных), и оно должно быть разделено между компонентами.

Одно из решений - использовать аннотации @Input, @Output и привязку данных для передачи изменений состояния другим компонентам. Две проблемы с этим подходом: одна, если в приложении больше трех компонентов, задача становится утомительной и сложной (и, как следует из названия этого блога, я ненавижу сложность), две вы не можете передать или передать событие из компоненты, которые используются в качестве пути маршрутизатора. Этот вариант не подходит для чего-то более сложного - домашней страницы, которую я делаю для своего дедушки :).

Второй вариант - воспользоваться услугами. Теперь этот вариант более жизнеспособен. Вам, по крайней мере, не нужно беспокоиться о ‹router-outlet›, если вы правильно разберетесь с областями, вы можете внедрить сервис везде, где вам это нужно. Проблема в том, что это просто только вначале. По мере роста вашего приложения растет и количество сервисов. Объект, который представляет состояние, изменяющееся под вами (вы можете исправить это с помощью immutable.js), затрудняя отслеживание ошибок. И все это превращается в беспорядок, который кто-то захочет переписать.

Я рекомендую третий вариант - использовать NgRx. Это очень простая библиотека, которая позволяет нам хранить состояние в одном месте и внедрять единую службу, которая позволяет нам отслеживать изменения. Он использует RxJS для передачи изменений в наше состояние, модель данных, чтобы она вписывалась в самоуверенный угловой ландшафт. Он неизменен, поэтому все ошибки, которые возникают при работе со сложным состоянием, исчезают. Если ваш проект будет большим, я рекомендую с самого начала внедрить NgRx. Реорганизовать рефакторинг намного проще, когда новые требования бизнеса вынуждают разработчика менять свои прекрасно продуманные структуры :). Я не буду рассматривать эту чрезмерную инженерию, как вы увидите через минуту, используя NgRx, просто используя службы, но более аккуратно.

Как начать

Есть много хороших руководств, но я хочу, чтобы эти статьи были последовательными. И это будет частью демонстрации того, насколько просто использовать ngrx. Установите его:

npm install @ngrx/store

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

//AddToMenu.action.ts
export class AddToMenu implements Action {
  readonly type:string = '[Menu] addToMenu'
  constructor(payload:any){
    this.payload = payload;
  }
}
//MenuReducer.reducer.ts
export function menuReducer(state = {appState:null}, action:Action){
  if(action.type == '[Menu] addToMenu'){
    return {appState: action.payload};
  }
}
//Menu.component.ts
export class MenuComponent implements OnInit {
  constructor(private store$:Store<Action>){}
  ngOnInit(){
    this.store$
      .subscribe(t => console.log(t.payload))
  }
}

Затем вам нужно зарегистрировать этот редуктор, но я не хочу писать полностью работающий пример, так как существует множество хороших ресурсов (https://ngrx.io/guide/store). Я пытаюсь подчеркнуть, что использовать NgRx действительно легко. Но все становится немного сложнее :)

Как мы растем !!!

Большим преимуществом NgRx является то, что хранилище неизменяемо. Это означает, что каждый раз, когда редуктор запускается, int должен возвращать все новое состояние (не для всего приложения, а за то, что он несет ответственность). Состояние нашего приложения - это просто объект json, и каждый редуктор создает запись на первом уровне. Итак, используя пример ниже, чтобы представить состояние в нашем приложении:

{
  "image":
  {
     "url": "images/0001.jpg",
     "width": 200,
     "height": 200
  },
  "thumbnail":
  {
     "url": "images/thumbnails/0001.jpg",
     "width": 32,
     "height": 32
  },
  "batters":
  {
    "batter":
      [
	{ "id": "1001", "type": "Regular" },
	{ "id": "1002", "type": "Chocolate" }
      ]
    },
    "topping":
      [
	{ "id": "5001", "type": "None" },
	{ "id": "5002", "type": "Glazed" },
	{ "id": "5005", "type": "Sugar" }
      ]
   }
}

Мы можем выделить 3 редуктора: Image.reducer.ts, Thumbnail.reducer.ts и Batters.reducer.ts. Теперь, если мы хотим добавить или изменить выделенный топпинг, это означает, что весь объект масла должен быть возвращен из редуктора. с этим единственным обновленным свойством. Как вы понимаете, ваш редуктор может очень быстро усложниться из-за большого количества вложенных операторов if else. Итак, мой совет 1: если у вас есть доступ ко всему объекту, передайте его своему редуктору (не только изменения, а попытайтесь выяснить, что обновить). Это упростит жизнь. совет 2: постоянно думайте о структуре состояний и при необходимости выполняйте рефакторинг. Как бывший разработчик баз данных я могу сказать, что существует общее опасение, когда дело доходит до рефакторинга существующей модели данных. Разработчики предпочитают добавлять тонны кода, чтобы не испортить его. И поскольку хранилище немного похоже на базу данных (только с одним пользователем), я настоятельно рекомендую вам сохранить его как можно более плоским. Лучше иметь много коротких и лаконичных редукторов, чем один гигантский. Проповедник чистого кода долгое время проповедует краткий метод, и, поскольку сокращение - это всего лишь методы, они должны подчиняться тем же принципам.

Как мы растем часть 2 :)

Во второй части мы поговорим о другой части уравнения. Состояние чтения. В приведенном выше примере я написал:

export class MenuComponent implements OnInit {
  constructor(private store$:Store<Action>){}
  ngOnInit(){
    this.store$
      .subscribe(t => console.log(t.payload))
  }
}

Этот console.log будет запускаться каждый раз, когда в магазине происходят какие-либо изменения. А мы этого не хотим. Этот компонент следует уведомлять только тогда, когда произошли изменения, которые его интересуют. На диаграмме выше есть кружок прямо под компонентом, который читает селекторы. И это именно то, что мы собираемся использовать.

export class MenuComponent implements OnInit {
  constructor(private store$:Store<Action>){}
  ngOnInit(){
    this.store$.pipe(select('batters'))
      .subscribe(t => console.log(t.payload))
  }
}

Приведенный выше код будет запущен только тогда, когда будут внесены изменения в часть нашей модели, предназначенную для работы с отбивающими элементами. Хотя мы передаем select методу pipe, это не функция RxJs. Этот оператор предоставляется @ ngrx / store. Итак, совет 3: делайте только то, что вам нужно. «Дело не в том, как вы храните, а в том, как вы это просите».

Но все дело в том, что вы храните. он же: S.H.A.R.I.

Если честно, этот абзац будет копией этого разговора. Но как сказал Пикассо: Хорошие художники берут взаймы, великие художники воруют. :). К тому же я использовал принцип S.H.A.R.I. и нашел его завершенным. Посмотрим правде в глаза, NgRx сложен (он настолько сложен, насколько и должен быть, чтобы сохранить ясность и многословность), поэтому нет смысла складывать все в магазин. Вот несколько принципов, которыми мы руководствуемся в процессе принятия решения.

Shared - общее состояние, означающее, что не используется ни одним элементом. Я большой сторонник разделения бизнес-логики и логики отображения на интеллектуальные и презентационные компоненты. И я предпочитаю иметь отношения один на один. Один компонент презентации обернут вокруг одного интеллектуального компонента. И у них есть собственный способ обмена данными. Поэтому, когда я использовал термин «элемент», я объединял их в две группы. Отличным примером общего состояния являются данные авторизации. Он понадобится везде.

Гидратированный - он должен быть доступен при следующей загрузке. Так что это все случаи, когда в игру вступают sessionstorage или localstorage. Настройки пользователя - хороший тому пример.

Доступен - я упоминал ранее, что состояние доступа только к одному компоненту не должно сохраняться в хранилище, но есть исключение. Если данные для рендеринга этого компонента поступают из route. Особенно, если вам нужно обратиться к бэкэнду за этой информацией.

Получено - я уже упоминал об этом раньше. Если вы вызываете внешний API для получения некоторых данных, эти данные становятся хорошим кандидатом для хранения. Честно говоря, это немного суждение. Есть и другие способы кэширования и повторной передачи данных, полученных с помощью http.get (‘htps: //…’). Например, с помощью publishReplay (1), refCount (). В экосистеме NgRx есть крутой механизм, который позволяет перенести эти побочные эффекты в другой фрагмент кода. Таким образом, сохраняя чистоту наших компонентов, они называются @ ngrx / effects, но я не буду об этом здесь говорить.

Воздействие - состояние, на которое влияют действия других магазинов. Честно говоря, я думаю, что это немного избыточно (хорошо сочетается с общим состоянием), и я думаю, что аутхоры просто хотели описать это мнемоническое устройство определенным образом. Так же, как Щ.И.Т.

Итак, теперь к миру моего личного мнения. Чего нельзя складывать в магазин. Формы номер один. Это немного спорно. Например, этот разговор дает другую точку зрения. Но мне кажется, что API реактивных форм Angular (который я рекомендую использовать) является собственным из хранилища. Однако формы содержат полную информацию о состоянии, и мы знаем, что это хороший кандидат для настойчивости. Я слышу этот аргумент, но Ват в конечном итоге убеждает меня в том, что контекст обычно самодостаточен, он нигде не используется. Также элементы управления angular, которые делают форму более сериализуемой или неизменной, будут меняться без вашего ведома. Совет 4: подумайте, что поставить в магазин.

Соглашение об именах и некоторые полезные шаблоны.

Этот абзац будет ссылкой. Виктор Савкин - один из самых умных парней в сообществе angular. В этой статье он дает отличный совет о том, как некоторые из самых популярных шаблонов обмена сообщениями переводятся в NgRX. Он берет примеры из книги, написанной в соавторстве с Мартином Фаулером, и переводит механизмы, которые можно найти в больших и малых центрах обработки данных в разных организациях, в локализованный шаблон для одного пользователя. И я не могу ничего улучшить.



NgRx - это не серебряная пуля. Резюме.

Фред Брукс в 1989 году написал статью «Нет серебряной пули - суть и случайность в разработке программного обеспечения». Там он исследует причины, по которым производительность разработчиков не может масштабироваться с той же скоростью, что и закон Мура, описывающий масштабирование оборудования. Все сводится к тому, что мы имеем дело с проблемами реального мира, которые столь же сложны, как и есть. И мы не можем сделать их проще. Я имею в виду, что создание банковского счета требует 50 шагов (требуемых регулирующими органами, материнской компанией и т. Д.), И мы никогда не сможем уменьшить это количество. Это суть бизнес-проблемы, которую мы пытаемся решить. Мы правильно называем это существенной сложностью. Есть также случайная сложность, которую можно полностью уменьшить, так как это ошибка разработчика. Этот блог - мой способ борьбы с этой формой глупости.

Как бы то ни было, я начал отходить от темы. Я считаю, что NgRx при правильном использовании (или, по крайней мере, постоянном) может помочь нам уменьшить случайную сложность. Помогая нам избежать ада внедрения сервисов и ада мутаций, которые неизбежно произойдут, если мы попытаемся решить их самостоятельно.