AngularInDepth уходит от Medium. Более свежие статьи размещаются на новой платформе inDepth.dev. Спасибо за то, что участвуете в глубоком движении!

За последнее десятилетие фронтенд-разработка сильно изменилась. С выходом AngularJS в 2009 году декларативное представление пользовательского интерфейса стало стандартным подходом для интерфейсной веб-разработки. Angular делает декларативное программирование первоклассным гражданином за счет использования шаблонов в компонентах. Использование статических шаблонов для определения логики имеет ряд преимуществ, и мы рассмотрим их в этой статье.

Однако существует альтернативный подход к описанию представления пользовательского интерфейса через определение представления на основе выражений, известное как Virtual DOM. Этот подход использует JavaScript для определения представления и является подходом, реализованным React. В этой статье я хочу описать это и представить библиотеку, которую я написал для использования этого подхода в Angular.

Преимущества шаблонного подхода

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

Возможно, вы знакомы с двойственностью компонента / шаблона из своего опыта работы с контроллером модель-представление-представление (MVC) или модель-представление-представление-модель (MVVM). В Angular компонент играет роль контроллера / модели представления, а шаблон представляет представление.

Компонент и его шаблон связаны общедоступной формой Component, тогда они вместе могут создавать экземпляр View. Простой компонент Angular может выглядеть так:

Со своим шаблоном:

Определение представления с помощью шаблонов имеет много преимуществ.

Разделение озабоченности

При написании шаблонов можно полностью сосредоточиться на пользовательском интерфейсе, не беспокоясь об источниках данных, их графике зависимостей и логике представления. Например, датапикер шаблон в Angular Material выглядит так:

Шаблон только определяет структуру и содержимое, опуская детали реализации. Истинным источником periodButtonText может быть компонентный вход, внутреннее состояние или вычисленный результат, который не имеет значения для шаблона.

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

Простота

С уровнем абстракции шаблон может помочь упростить поток управления с помощью простого атрибута в теге:

Напротив, реализация потока управления внутри выражений затруднительна и неизбежно требует дополнительных условных операторов или обратных вызовов. Даже при использовании JSX Control Statements, чтобы оставаться декларативным, дополнительный уровень вложенности все равно нарушит историю VCS.

Кроме того, с идеей каналов (возможно, известных как фильтры в других системах) можно элегантно обрабатывать сложные структуры данных, такие как Observables:

Вместо того, чтобы повторять subscribe и unsubscribe при каждом потреблении.

Последовательный `this`

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

Оптимизация производительности

Обладая конкретным знанием того, какие части шаблона являются динамическими, шаблон:

Будет скомпилирован в:

Так что на этапе обновления нужно обрабатывать только привязки.

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

Однако есть некоторые ограничения для подхода с шаблоном при наличии сложного потока управления:

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

Новая возможность

Учитывая, что есть некоторые условия, с которыми нельзя элегантно справиться с помощью шаблонного подхода. Можем ли мы сделать возможным написать определение представления на основе выражений в Angular, когда это необходимо? Ответ - да. И здесь нам пригодится наша библиотека. Когда мы используем библиотеку, нам просто нужно перейти от Renderable и реализовать метод render, аналогичный React. Вот пример:

Эта реализация приведет к отображению строки “Hello, Angular!” в браузере. (Живой пример)

И Angular, и Angular CLI уже поддерживают файл .tsx. Более того, можно фактически изменить tsconfig.json с помощью другого параметра jsxFactory вместо того, чтобы писать import * as React в компоненте.

Помимо внутренних элементов, могут быть отрисованы как компонент класса, так и компонент функции (пока не полностью совместим с React API). Но самое главное, можно отрендерить еще один канонический Angular Component (живой пример):

HelloComponent это простой компонент Angular, в котором нет ничего особенного, но в настоящее время нам нужно зарегистрировать его в entryComponents. В текущей реализации Angular компонент разрешается через ComponentFactory. Если он не найден, он будет рассматриваться как функциональный компонент, что приведет к ошибке.

После Айви концепция entryComponent будет значительно улучшена.

Отображение здесь можно проиллюстрировать как:

  • Опора: foo - ›\ @Input (‘ foo ’)
  • Опора: onBar - ›\ @Output (« бар »)
  • Реквизит: дети - ›Проекция контента

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

Полнофункциональный пример TODO MVC, использующий эту библиотеку, можно найти на GitHub с доступной онлайн-версией.

От шаблона к ViewModel

Чтобы понять, как виртуальные DOM могут обрабатываться в Angular, мы должны сначала понять, что такое Virtual DOM. Вкратце, Virtual DOM - это просто лишнее имя для ViewModel, которое содержит значения для динамических частей шаблона. Учитывая полностью статический шаблон, его ViewModel пуст:

Когда мы определяем все свойства элемента как динамические и извлекаем их в привязки, ViewModel становится контейнером свойств и их значений:

Тогда связывающий источник данных можно сделать «контейнером свойств», а не отдельными признаками:

Наконец, когда имя тега также является динамической привязкой, шаблон больше не имеет статических частей и фактически становится избыточным:

Когда ViewModel несет всю информацию о View, она становится определением View. Независимо от того, как он называется, это все еще ViewModel и с ним можно работать как с любой другой ViewModel. Самая важная часть - это определение того, что изменилось при прохождении проверки изменений. Это обнаружение изменений в Angular или согласование в React.

Как все это работает

При рассмотрении реализации директив обработки сложных структур данных, таких как NgForOf, NgClass и NgStyle, легко заметить, что для обнаружения изменений нам нужно различать последовательные входные значения и применять изменения, если они обнаружены.

Хотя большинство фреймворков содержат некоторые разные реализации, Angular предоставляет их разработчикам для повторного использования. В Angular есть два типа различий:

В то время как директива Angular обычно использует только одно различие (NgClass использует любое из них, зависит от его ввода, но не оба), структура виртуального DOM намного сложнее и вложена, поэтому необходимо использовать оба различия: KeyValueDiffer для реквизита и IterableDiffer для детей. Подробная реализация здесь не проиллюстрирована, но концептуально они просто рекурсивное различие.

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

Пока мы используем Renderer методы для манипуляций с пользовательским интерфейсом, любая реализация будет оставаться независимой от платформы и может использоваться на любой платформе без специальной обработки.

Для рендеринга компонентов Angular нам нужно использовать функциональность Angular Dynamic Components, наиболее важные части ComponentFactory и ComponentFactoryResolver.

ComponentFactory предоставляет необходимые метаданные ввода / вывода, обеспечивая работу сопоставления свойств:

Фактически, часто используемые пакеты, такие как ngUpgrade и Angular Elements, созданы на основе функциональности динамических компонентов.

Благодаря возможности рендеринга Virtual DOM нам все еще нужно сообщить Angular, когда это сделать. Затем следует крючок жизненного цикла - DoCheck.

Как специально названный обработчик (все остальные начинаются с «on» или «после»), DoCheck предназначен для обработки дополнительных заданий, когда изменение обнаружение запускается, а это именно то, что мы хотим. Итак, основная структура Renderable будет следующей:

Теперь мы можем использовать метод render в компоненте Angular для рендеринга другого компонента Angular, ничего волшебного.

План на будущее

Хотя можно организовать компоненты Angular с помощью метода render, есть еще много функций, которые нужно сделать.

Поддержка атрибутов HTML

React API для внутренних элементов представляет собой смесь свойств DOM, атрибутов HTML и переименованных свойств DOM. (пока игнорируем систему событий)

Хотя переименованные свойства DOM спорны, атрибуты HTML определенно необходимы:

Атрибуты data- или aria- не имеют соответствующих свойств, без поддержки атрибутов они могут быть добавлены только ref обратными вызовами (также пока не поддерживаются), что не является декларативным подходом.

Совместимость React / React DOM API

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

Обратный мост

Используйте существующие компоненты класса или функциональные компоненты в шаблоне Angular без создания объекта Virtual DOM. Вероятно, это имеет смысл только после завершения поддержки совместимости.

Глобальный бутстрап

Может быть полезно визуализировать структуру Virtual DOM без написания кода Angular. Фактически это было реализовано в более ранних версиях, но было удалено в ходе недавнего рефакторинга из-за сложности процесса сборки.

Основная концепция здесь такова: из-за отсутствия определяемого пользователем типа Angular весь процесс AOT может быть перенесен на процесс сборки библиотеки, а затем опубликован код, скомпилированный AOT. Пользователю больше не нужно будет переносить компилятор в среду выполнения.

Резюме

Шаблоны великолепны, нет необходимости писать определение представления на основе выражений, если оно не является чрезвычайно сложным.

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

Репозиторий библиотеки можно найти на GitHub, хотя технически это не первый выпуск, он больше не является POC, и я призываю вас поэкспериментировать с ним (но пока не рекомендуется для производства). Не стесняйтесь отправлять отчет об ошибке или запрос функции.