Привет DevPRO! Сегодня я хочу рассказать вам про Renderless Components в React.
Для начала - немного о себе: меня зовут Сухушин Александр. Я - Frontend-разработчик в компании Userstory (навести на ссылку). Наша компания занимается проектированием и разработкой сайтов и комплексных информационных систем для автоматизации бизнеса, (убрать наведение с сылки) а также решениями коммерческих и маркетинговых задач.
Мы делаем фронтенд на React.
В своём последнем проекте нам нужно было работать с картами, а также обновлнять состояние на клиенте по WebSocket. Мы использовали Leaflet, на данный момент - это самая известная OpenSource библиотека для работы с картами. Для работы с WebSocket мы использовали билиотеку Centrifuge. Основная проблема этих двух библиотек то, что у них нет реализации для React.
На самом деле есть React-Leaflet, но нам нужно было использовать сторонние плагины, которые он не поддерживал и не давал нужной гибкости. Что же делать?
Для начала давайте посмотрим, как работает сам LeafLet, заглянем в официальную документацию:
Чтобы создать карту, нужно создать div
, который будет выступать в качестве контейнера для карты.
Далее в JavaScript нужно вызвать метод L.map()
, куда передать идентификатор контейнера или ссылку на его узел DOM.
Также, нужно спозиционировать карту, в данном случае задаются координаты центра и масштаб.
И ещё нужно насторить Тайл-сервер - это сервер, который будет возвращать картинки для карты.
Мы можем реализовать это в React следующим образом: В методе render, мы срздаём div
и сохраняем ссылку на него с помощью ref.
В componentDidMount
мы вызываем метод L.map()
, куда передаём ссылку на созданый контейнер, и сохраняем экземпляр в state
.
В componentWillUnmount
мы удаляем созданный экземпляр карты.
Так же через контекст мы передаём дочерним элементам созданый экземпляр leaflet
.
Тайл-сервер: render
- null
. В componentDidMount
, мы добавляем слой тайл-сервера, в componentWillUnmount
- его удаляем.
В componentDidUpdate
- удаляем старый слой и добавляем новый. добавление и удаление слоёв - это уже вызовы методов Leaflet,
которые можно посмотреть в документации.
Подобным же образом выводятся объекты на карте. На componentDidMount
, мы добавляем слой, на componentWillUnmount
- удаляем его.
На componentDidUpdate
- удаляем старый слой и добавляем новый.
Для описания объектов на карте используется формат GeoJSON. По сути, это обычный JSON, с определённой структурой. Для примера, я описал несколько объектов:
С позиционированием немного посложнее: Самый простой способ - это задать координаты центра и масштаб,
но по проекту требовалось позиционировать карту, чтобы у неё был максимальный масштаб, но при этом в экран попадали все объекты.
В componentDidMount
я вызываю метод fly()
, который получает view
- это объект или массив объектов в формате GeoJSON.
Из view
мы получаем bounds
.
bounds
- это минимальная прямоуголяная область, которая включает все объекты.
Если bounds
- корректный - мы позиционируем карту на нём, иначе показываем весь мир.
В componentDidUpdate
я проверяю, изменился ли view
, изменился ли bounds
если да, вызываю fly()
.
И также я добавил обработчик измнения позиционирования карты, который вызывает onViewChange
, если он был передан в props
.
Я вижу, вы уже немного устали от кода. На самом деле есть такая тема, когда долго смотришь на код (нажать картинку), код начинает смотреть на тебя.
Давайте посмотрим пример, как это выглядит в использовании: Выводится компонент Map, а внутри него - TileLayer
, View
, и объекты на карте.
И, наконец, посмотрим на результат, можете перейти по этому коду: (подождать пару минут) Здесь мы можем менять позиционирование карты, при этом фиксируется текущее положение. Можем менять тайл сервер, например есть такой, и даже такой... Также можем отображать на карте разные объекты: например, маркер, где проходит DevPRO, маркер, где находится наш новый офис, и путь, между ними. При этом, как вы видите, карта автоматически позиционируется по выводимым объектам. (Закрыть Ctrl+W)
Как вы заметили, во многих компонентах, я ничего не выводил, а пользовался только методами жизненного цикла.
Такой подход я назвал Renderless Components. И он позволяет интегрирровать практически что угодно:
Например так я интегрировал WebSocket:
componentDidMount
- я подключаюсь к серверу центрифуги,
componentWillUnmount
- отключаюсь, и передаю экзепляр центрифуги через контекст дочерним элементам.
Аналогично - компонент для подписки componentDidMount
- подписывемся, componentWillUnmount
- отписываемся.
Во время подписки - регистрируем обработчик сообщений.
В использовании это выглядит вот так: Мы оборачиваем всё в компонент Centrifuge
, куда передаём данные для подключения.
Внутри выводим компонент Subscribe
, куда передаём название канала и обработчик сообщения.
По WebSocket мы будем получать координаты точки и выводить её на карте.
Я подготовил пример, как по сокетам мы будем получать координаты точки (подождать пару минут):
По этомим кодам ссылки на презентацию и исходный код примеров. На этом у меня всё, пожалуйста ваши вопросы.