Почти год назад мы говорили с клиентом о создании платформы социального сообщества, подходящей для его интрасети. У них также было существующее внутреннее мобильное приложение для более чем 100 000 сотрудников, и они хотели, чтобы социальное сообщество было интегрировано в существующее приложение. К нашему приятному удивлению, они выбрали React Native в качестве кроссплатформенной технологии.

С React Native на мобильных устройствах React был логичным выбором для Интернета. Это позволило одной команде над проектом работать как над одностраничным, так и над мобильным приложением. В идеальном мире эта одна команда могла бы написать код только один раз, поскольку с React и JavaScript это одна и та же технология на всех платформах.

На самом деле мы потратили некоторое время на изучение того, какие части приложения React могут быть легко доступны для обмена между Интернетом и React Native, и задокументировали наши результаты здесь:



Наши ожидания, как все пойдет

После нашего исследования мы остановились на частично совместно используемой кодовой базе. Мы ожидали, что мы сможем совместно использовать весь связанный с Redux код (создатели действий, редукторы, селекторы и т. Д.) И бизнес-логику, но никакие визуальные компоненты пользовательского интерфейса. Тем не менее, для такого сложного приложения, как социальная сеть, мы ожидали, что при таком подходе будет написано намного меньше кода.

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

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

Как не сломить сразу всех своих потребителей

При написании библиотеки по сравнению с приложением необходимо учесть множество соображений при выпуске новой версии. Даже самое незначительное изменение может повлиять на потребителя, если было выпущено изменение API, ориентированное на потребителя, а версия пакета не обновлена ​​должным образом. npm по умолчанию устанавливает пакет, номер версии которого имеет префикс каретки (^1.2.0), что позволяет npm автоматически обновлять пакет в определенном диапазоне. Если бы все сопровождающие внедрили Семантическое управление версиями, это было бы хорошей идеей. К сожалению, это не так.

Чтобы избежать проблем с двумя нашими клиентами-потребителями, мы решили автоматизировать релизы с помощью semantic-release. semantic-release может на основе истории git репозитория определить, были ли реализованы критические изменения, и семантически значимым образом увеличить номер версии.

Хотя хороший рабочий процесс выпуска очень полезен, он не поможет интегрировать общий пакет во время разработки. Принципы разработки программного обеспечения, такие как TDD, могут очень помочь в этом, но пользовательская история обычно имеет больше критериев принятия, связанных с пользователем. Поэтому обычно код в разделяемой библиотеке необходимо протестировать хотя бы на одном приложении-потребителе. Такие инструменты, как Lerna, помогают в разработке общих пакетов, но мы решили сами связать пакеты с npm link и запускать необходимые задачи вручную (например, шаги компиляции TypeScript). Это тоже сработало неплохо, за двумя заметными исключениями:

  1. Папка node_modules в зависимости приводит к сбою во время преобразований Babel в React Native.
  2. Metro, Пакетатор JavaScript для React Native, не поддерживает символические ссылки, как описано в самом первом выпуске репозитория. И, к нашему счастью, npm link делает именно это; сделайте символическую ссылку на ваш местный код на node_modules. Это означает, что Metro не может правильно прочитать и интерпретировать код.

К сожалению, мы так и не нашли правильного решения нашей проблемы. В итоге мы протестировали изменения в общей библиотеке в Интернете, а затем установили пакет «вручную» в приложении React Native для локального тестирования.

Бросить вызов нашим собственным передовым методам Redux

Во время реализации мы собрали и объединили лучшие практики из онлайн-статей и наш собственный опыт работы с Redux. Мы уже закончили пару крупных проектов React (+ Redux) и (думали, что) были хорошо подготовлены к поставленной задаче. Оглядываясь назад, можно сказать, что некоторые вещи мы поступали бы совсем по-другому.

Списки идентификаторов

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

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

Во время разработки мы начали тестировать, можем ли мы пропускать массивы идентификаторов и напрямую работать с селекторами. Например, selectPostsForUser(id: number) будет перебирать все доступные сообщения, соответствующие идентификатору пользователя, вместо того, чтобы искать объект пользователя и читать из списка сообщений, на которые есть ссылки. К сожалению, производительность от этого может сильно пострадать. К счастью, сообщество React предлагает такие библиотеки, как Reselect, которые помогают с помощью мемоизации.

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

Срок службы данных

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

В 2017 году на YouTube каждую секунду загружалось 400 часов видео. На практике контент не только создается, но также редактируется и удаляется. Классическая платформа с рендерингом на стороне сервера позволяет пользователю автоматически получать новейший контент. В среде SPA ранее полученный контент должен кэшироваться на клиенте, но при этом периодически обновляться.

Мы попробовали несколько разных подходов к решению этой проблемы. Сначала мы проверили, действительны ли данные, непосредственно перед рендерингом, и отправили повторную выборку, если они устарели. Как объясняется в результатах нашего исследования, мы решили не делиться контейнерами Redux, поэтому нам фактически пришлось реализовать эти изменения в Интернете и в приложении React Native. Мы быстро поняли, что проводим одни и те же проверки данных повсюду, и решили пойти другим путем.

В итоге мы оставили это решение, но поняли, что оно не совсем оптимально для пользовательского опыта платформы. Хотя мы позаботились о том, чтобы обновления отправлялись только при определенных взаимодействиях с пользователем, они все равно могут приводить к странному поведению пользовательского интерфейса. Лучшим решением могло бы быть уведомление пользователя о новых данных, но только обновлять их при явном действии пользователя, как это делает, например, Twitter.

Перспективы

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

С ростом популярности Универсальных систем дизайна и их технических реализаций, многоразовые компоненты пользовательского интерфейса очень близки. Сегодня постоянно разрабатываются несколько способов достижения этой цели. С react-native-dom Интернет можно рассматривать как цель сборки приложения React Native, но это очень экспериментально. Также, что очень важно для нас, теряется семантическое значение элементов HTML. Нативный View может быть div, article, main, aside или почти любым другим в HTML.

Другой подход - Progressive Web Apps. Прогрессивные веб-приложения или PWA - это обычные веб-приложения, которые предлагают нативные функции, такие как автономное поведение, push-уведомления и доступ к оборудованию устройства. В следующем проекте мы оценим, сможем ли мы создать прогрессивное веб-приложение на основе требований, но обязательно снова обратимся к общей библиотеке, если PWA будет отсутствовать.