Angular: повторяющиеся проблемы, с которыми я сталкиваюсь как консультант по Front-End

Привет, меня зовут Микеле Стивен, фронтенд-разработчик и консультант из Италии.
За последние 2 года я 90% времени работал с Angular, так как это одна из самых востребованных технологий в мире. это поле в моей стране. Я хотел бы кратко рассказать о проблемах, с которыми я чаще всего сталкиваюсь как консультант, и хотел бы затронуть некоторые проблемы или вопросы, которые мне задают, возможно, слишком часто!

Некоторые из этих моментов относятся только к Angular, другие могут быть применимы к любому другому фреймворку, библиотеке или даже простому JavaScript.

Сразу по делу!

Грязный код

Я могу показаться немного высокомерным в этом вопросе, но это настолько важная тема, что она сводит меня с ума, если к ней относиться легкомысленно: нельзя недооценивать важность написания чистого кода. Мне плевать, если вы пользуетесь фреймворком и считаете себя продвинутым разработчиком только из-за этого: если я не могу понять ваш код, значит, проблема. Мне плевать, если вы говорите, что понимаете это: на самом деле, в 90% случаев вы просто лжете себе (искусно или нет), и если вы взглянули на код, который вы написали за несколько недель до этого, держу пари, ты не смог. Ваша первая задача как профессионального разработчика - написать чистый и понятный код. Забудьте о фреймворках, забудьте о библиотеках: есть кое-что поважнее. Если вы этого не понимаете, вы плохо справитесь с работой своего начальника, которому придется выбросить все, что вы сделали за последние месяцы, и попросить помощи у консультантов. В качестве общего правила:

  • Напишите комментарии, объясняющие, что делает функция / класс / метод / свойство. «Но мы называем вещи значимыми именами, нам не нужны комментарии!», неверно. Если вы говорите о четырехстрочной функции, возможно. По всему остальному оставляйте комментарии.
  • Пишите содержательные комментарии. Будьте умны: если вы используете машинописный текст, вы можете получить документацию по своему программному обеспечению почти бесплатно. Пишите содержательные комментарии, потому что они БУДУТ вашей документацией (используйте compodoc для Angular, в противном случае typedoc или любой другой генератор, который может работать с вашими инструментами)
  • Поместите файлы в правильные каталоги / модули. Разве не странно, что что-то в вашем CoreModule использует что-то, что находится в shared / toolbar / models ? Ага, это так. Проясните, что должно использоваться в качестве внутренней зависимости в вашем проекте, а что нет. Если вы пишете модуль, создайте файл index, экспортирующий только то, что должны видеть другие модули. И даже если вы технически можете импортировать что-то из модуля, прежде чем делать это, спросите себя, правильно ли это.
  • Используйте линтер (eslint, tslint…). Недавно у меня был спор с кем-то, кто не использовал линтер, потому что он был «создан для неопытных разработчиков, которые не знают, как называть вещи». Какой кошмар.
  • Взгляните на основные понятия JavaScript / ES6 +, которые вы, возможно, пропустили: пересмотрите унарные и тернарные операторы, узнайте, как использовать условные выражения для обработки значений по умолчанию, узнайте, как выполнять нулевые проверки, деструктуризацию и т. д. I заметил, что то, что я считал «базовым», другим казалось «уловками».

Презентационные компоненты, не являющиеся презентационными

В вашем приложении 80–90% компонентов должны быть презентационными. Компонент представления должен принимать входы и выдавать выходы, ничего больше. Да, у него может быть свое маленькое внутреннее состояние: это не проблема. Например, для TabsComponent вполне нормально принять индекс извне, но сохранить currentIndex в свойстве. Если у него есть выход indexChange, который сообщает родителю об изменении (бонус: наличие пары index / indexChange позволит вам использовать синтаксис двусторонней привязки [(index)] в Angular!). Делает ли это компонент менее безгражданным? Да, но это все еще управляемо извне, а также может работать само по себе. Победа - победа.

ЭТО то, чего вам не следует делать в своих презентационных компонентах:

  • Наличие зависимостей от других модулей (кроме Angular или «действительно основных»).
  • Измените входные данные напрямую: если вашему компоненту задан массив или объект, его изменение также повлияет на данные родителей. Я не буду вдаваться в подробности об этом, но предположим, что вы используете Redux, и этот объект берется из селектора Store. Как вы думаете, что произойдет? Да, вы бы изменили Магазин прямо из невинного презентационного компонента. (psssst, используя Angular и ngrx? Включите ngrx-store-freeze как можно скорее). Если вам нужно изменить входные данные, клонируйте их. Это слишком дорого? Возможно, возникла проблема с дизайном.
  • Используйте модели из других модулей только потому, что они имеют одинаковую форму: ваш FilterComponent использует интерфейс ToolbarFilterItem, который поступает из другого модуля? Не должно быть. У него должен быть собственный интерфейс FilterItem, даже если он идентичен. Если разработчик работает над ToolbarModule, он / она не сломает ваш компонент. Да, кстати: научитесь работать с ветками (git или что-то еще), даже если вы не в команде. Поверьте мне.
  • Без использования ChangeDetectionStrategy OnPush. Вы должны иметь возможность безболезненно включать его в своих презентационных компонентах, возможно, вам понадобится использовать ChangeDetectorRef для запуска компакт-диска, если ваш компонент имеет внутреннее состояние, это зависит от обстоятельств. Это отличный помощник для повышения производительности, особенно если вы привязываете функции в своих шаблонах Angular (делайте это только с помощью OnPush!). Если вам нужны производные данные, примените геттеры / сеттеры к своим входам или используйте ловушку жизненного цикла ngOnChanges. Однако, если вы используете OnPush, вы можете сделать это, не особо беспокоясь:

Однако вам следует предпочесть методы получения / установки / ngOnChanges по очевидным причинам (в приведенном выше примере OnPush запускает обнаружение изменений для каждого изменения ввода, даже если оно не связано с отфильтрованными элементами).

Огромные контейнеры

Что такое контейнер? Это компонент, в котором размещены все презентационные компоненты этого «представления» или маршрута: он единственный, который должен использовать службы для получения / обновления данных, который должен использовать хранилище Redux, которое должно отправлять действия и которое должно передавать свои внутренние состояние (или его фрагменты) дочерним компонентам. Все дочерние компоненты носят презентационный характер и не должны даже знать, откуда пришли данные или каковы будут последствия их выходных данных. Если завтра вы решите добавить библиотеку управления состоянием или избавиться от нее, ваши презентационные компоненты не сломаются.

Поскольку контейнеры могут «удерживать» 99% логики этого представления, они могут стать огромными и недоступными для обслуживания. Как решить эту проблему?

  • Делегируйте обязанности службе. Ваш InvoiceComponent (контейнер) выполняет много вычислений? Создайте InvoiceService, который будет обрабатывать сложные операции, преобразования и т. Д. Вы также можете сделать это с помощью презентационных компонентов, и если вам нужен экземпляр службы для каждого компонента, просто объявите его с помощью поставщика в самом компоненте, вместо того, чтобы предоставлять его на уровне модуля.
  • Ваши контейнеры не должны строиться на предположениях. Допустим, у вашего компонента есть кнопка, и чтобы знать, что делать при нажатии на кнопку, вам придется учитывать другие переменные (например, URL-адрес): не позволяйте контейнеру решать это! Единственное задание контейнера - сообщить, что кнопка была нажата (путем вызова метода службы или отправки действия Redux, например «ButtonClick»). Это служба или эффект Redux, который решает, что делать дальше, в зависимости от остального состояния приложения (например, нахожусь ли я на маршруте / invoices? Нажатие кнопки может создать новый счет. Находится ли я под / sizes? При нажатии той же кнопки может быть создана новая оценка).
  • Контейнеры могут быть разделены. Если представление вашего маршрута слишком велико и имеет совершенно разные разделы, создайте более одного контейнера, каждый со своими собственными данными (из служб или селекторов хранилища) и методами (методы службы или отправленные действия).

Компоненты с дублированной логикой

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

  • Директивы использования. В мире Angular у нас есть директивы, которые представляют собой компоненты без шаблона. Вместо этого они могут внедрить шаблон компонента, к которому они применяются! Другими словами, смотрите на директивы как на способ инкапсулировать поведения и повторно использовать их. Вам нужен ваш CardComponent, чтобы открывать модальное окно при нажатии? Не создавайте CardWithModalComponent, пожалуйста. Создайте директиву, которая прослушивает щелчки по элементу хоста и применяет ее к определенным компонентам. Нужны ли вашей Директиве данные? Передайте его в директиву или объявите некоторые другие входы в самой директиве и используйте их в том же элементе. Пример:

  • Использовать проекцию содержания. Он используется слишком редко, но это удивительная практика и полезный шаблон, который можно использовать с любой компонентной структурой или библиотекой. Возьмите приведенный выше пример CardComponent и предположим, что вы хотите создать элемент для отображения нескольких карточек:

Таким образом, нам не нужно дублировать нашу структуру CardComponent, и мы можем управлять всеми картами извне (входы, выходы, директивы применения и т. Д.), Оболочка может только обеспечивать некоторый стиль. (помещая их в ряд? Масонство? Карусель?).
Кроме того, вы можете использовать ContentChildren, чтобы получить QueryList всех CardComponents и реагировать на изменения.
Очевидно, вы все равно можете использовать пример «Не», если вам действительно нужен другой компонент.

Огромные формы

Если ваши формы Angular становятся огромными, вам следует учитывать только одну вещь:

  • Используйте ControlValueAccessor для создания подформ. Это то, чему вам нужно научиться, если вы создаете сложные формы. В Интернете вы найдете множество руководств, и даже если это может показаться пугающим, это на самом деле не: это просто интерфейс, который мы можем реализовать в наших компонентах, чтобы сделать они действуют как действительные FormControls. Это помогает сохранить единое состояние формы, это лучшая практика, она работает с шаблонными и реактивными формами одновременно, и, поверьте мне, она действительно проста в использовании. После того, как вы использовали его один раз, вы можете почти скопировать реализацию для других компонентов. Например, предположим, что у вас есть форма счета-фактуры, она может стать шаблоном компонента при создании пользовательских элементов управления:

Другими преимуществами этой техники являются:

  • Ваши подформы можно повторно использовать в других компонентах!
  • Это компоненты: вы все равно можете использовать входы и выходы для их настройки (возможно, app-invoice-item потребуется список всех ваших продуктов / услуг на выбор? это как входные данные! Вы создаете новый продукт непосредственно из app-invoice-item, потому что пользователь ввел что-то, чего нет в списке продуктов? Создайте событие, и контейнер создаст новый товар с услугой / действием!)
  • Вы можете проверить их снаружи, как обычный ввод (рекомендуется), или изнутри (если логика никогда не изменится), реализовав интерфейс Validator.
  • Поскольку состояние всей формы находится в одном месте (вместо разделения между подкомпонентами), очень легко сериализовать / десериализовать и сохранить в службе, хранилище, хранилище браузера, файлах cookie, базе данных и т. Д.
  • Это приятно;)

Недостаточное изучение RxJS

Angular основан на двух вещах: TypeScript и RxJS. Игнорировать их - не лучший вариант, если вы хотите использовать этот фреймворк с комфортом. Но если TypeScript довольно прост для того, кто раньше работал со статически типизированным языком (C, C ++, C #, Java…), RxJS может стать сложной темой даже для опытного разработчика. Реактивное программирование требует сдвига в вашем мозгу, но как только вы это поймете, это действительно окупается. Не смотрите на поверхность, а углубитесь, и вы обнаружите, что команда Angular включила это не просто для удовольствия. По моему скромному мнению, это, возможно, лучший выбор, который когда-либо делала команда. Вы можете обнаружить, что команда подарила вам Ferrari, и, возможно, вы всегда использовали его, как малолитражку.

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

  • Узнайте, как использовать операторы сглаживания (mergeMap, switchMap, concatMap, выхлопная карта) и как они подходят для различных сценариев
  • Узнайте, как объединить несколько потоков (combLatest, withLatestFrom, forkJoin, merge, race…)
  • Узнайте, как управлять подписками и как фильтровать потоки (отписаться, фильтровать, взять, takeWhile, takeUntil…)
  • Посмотрите на всех операторов: вы обнаружите, что вам не нужно их все запоминать, так как вы сможете угадывать тех, кого уже видели за несколько месяцев до этого. Вы также сможете угадать поведение некоторых из них, просто взглянув на их имя (например, если вы знаете, что делает takeUntil, догадались бы вы, что делает skipUntil?)
  • Изучите разницу между горячими и холодными наблюдаемыми и, наконец, поймите, почему это соглашение об именах на самом деле не охватывает все сценарии (возможно, стоит отдельная статья, как вы думаете?)
  • Узнайте, как использовать Subject, ReplaySubject, BehaviorSubject и AsyncSubject (совет: хотя объекты BehaviorSubject абсолютно не являются и никогда не будут во вселенной альтернативы Redux, вы можете рассматривать их как своего рода «состояние» с начальное значение;))

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

Заключение

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

Постскриптум: ты из Италии?

Недавно я решил открыть новый канал YouTube, который будет содержать несколько VLOG'ов и скоро будет содержать обучающие программы, советы и приемы, уроки и другие забавные вещи. "Вот". Я надеюсь найти время не только на то, чтобы снимать видео, но и на то, чтобы сделать доступными английские субтитры!