Сообщество ReasonML расширяется каждый день с растущим числом пользователей, и многие другие вдохновляются на внедрение. Большинство из этих последователей ранее писали код на JavaScript, и есть несколько веских причин для их перехода на ReasonML.
Несколько лет назад, с появлением React, разработчики JS открыли для себя концепции функционального программирования и его преимущества для параллелизма. В то время как Typescript все еще достиг совершеннолетия с его корнями в JS, для веб-разработки требовался строго типизированный язык, соответствующий основам функционального программирования. ReasonML был создан именно для этой цели, предоставляя лучшие среды JS и OCaml. Через FFI OCaml ReScript обеспечил отличные мосты между этими средами, что позволило разработчикам создавать прекрасные проекты с использованием ReasonReact. Это стало ключевым фактором популярности ReasonReact.
Среда JS богата множеством полезных библиотек, которые разработчики ReasonML часто считают необходимым использовать. В результате написание эффективных привязок становится решающим. Привязки — это API, которые позволяют разработчикам использовать библиотеки из других сред. FFI OCaml позволяет разработчикам использовать JS-библиотеки через эти привязки (документы ReScript). Несмотря на то, что ReasonML продолжает добавлять библиотеки в свои предложения, множество библиотек по-прежнему недоступны.
В этой статье будет продемонстрировано использование одной из таких библиотек под названием LeafletJS, для которой будут написаны и использованы привязки в соответствии с философией React. Цель здесь — отобразить карту и разместить на ней маркер с помощью компонентов React, используя функции из LeafletJS. Исходный код для этого упражнения доступен в файле bs-leaflet-cmp, который также можно использовать для первоначальной настройки.
Определив нужную библиотеку, следующим шагом будет ее включение в проект, как рекомендовано здесь, добавив следующий код в index.html
<link rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin="" />
<script
src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
Первая цель — создать компонент, отображающий карту. Чтобы достичь этого, первым делом нужно написать несколько привязок для необходимых функций из LeafletJS. Вот образец привязки, который также можно найти в MapBinding.re:
[@bs.scope "L"] [@bs.new]
external createMap: (string, Map.options) => Map.t = "map";
Это принимает строку, которая является идентификатором DOM для div, и, возможно, литерал объекта для параметров. Он создает и возвращает объект map
типа Map.t
.
Написание привязок также включает объявление типов ReasonML, совместимых с целевыми API, как показано выше. Чтобы облегчить это, ReasonML имеет несколько общих двунаправленных типов данных, что исключает необходимость в преобразователях данных.
Следующим шагом является создание компонента React MapCmp
с реквизитами рендеринга, который позволяет всем последующим элементам карты использовать объект map
. Этот компонент можно найти по адресу MapCmp.re. Он отвечает за создание экземпляра объекта map
для отображаемого map-div с последующим созданием и добавлением слоя листов на карту и отрисовкой дочерних элементов. Для этого useEffect0
для этого компонента записывается следующим образом:
React.useEffect0(() => {
let mapObj = MapBinding.createMap(
mapOptions.map_elem_id,
mapOpts
);
let tileLayer =
MapBinding.createTileLayer(
"https://tile.thunderforest.com/neighbourhood/
{z}/{x}/{y}.png?apikey=<ApiKey>",
{
attribution: "Maps ©
<a href='https://www.thunderforest.com/'>
Thunderforest
</a>
, Data ©
<a href='http://www.openstreetmap.org/copyright'>
OpenStreetMap contributors
</a>",
minZoom: 11,
maxZoom: 15,
},
);
MapBinding.addLayerToMap(tileLayer, mapObj)->ignore;
None;
});
После создания компонента для отображения карты следующей целью является создание компонента для размещения маркера. Первым шагом к этому является написание привязки для функции для создания маркера. Эту привязку можно найти в MarkerBinding.re:
external createMarker:
(~pos: LatLng.t,
~opts: option(Marker.options)=?,
unit) => Marker.t = "marker";
Следующим шагом будет написание компонента, который создает и добавляет маркер на данную карту. Для этого привязки используются в useEffect0
для этого компонента следующим образом:
React.useEffect0(() => {
let marker =
MarkerBinding.createMarker(
~pos=marker_props.location,
~opts=marker_props.marker_options,
(),
);
MarkerBinding.addMarkerToMap(marker, map) |> ignore;
dispatch(SetMyMarker(Some(marker)));
Some(() => MarkerBinding.removeMarkerFromMap(
marker, map
)->ignore
);
});
Важно отметить, что он удаляет маркер с карты при очистке. Существуют и другие функции и эффекты, которые можно добавить к компоненту маркера. Например, если позиция маркера обновляется в свойствах, то можно добавить useEffect
для обновления местоположения маркера в соответствии с:
React.useEffect1(
() => {
switch (state.marker) {
| Some(marker) =>
MarkerBinding.setMarkerLatLng(
marker, marker_props.location
) |> ignore
| _ => ()
};
None;
},
[|marker_props.location|],
);
Использование MapCmp
и MarkerCmp
показано в ExampleComponent.re в репозитории. Обратите внимание, что репозиторий содержит больше таких компонентов и функций. Эти компоненты используются, как показано ниже:
<MapCmp mapOptions={
map_elem_id: "map_div",
options: {
center: {lat: 13.0, lng: 77.60},
zoom: 12,
}}>
{map => <MarkerCmp
map
marker_props={
location: { lat: 13.0,lng: 77.60},
marker_options: None}>
</MarkerCmp>}
</MapCmp>
Сгенерированный результат выглядит следующим образом:
Целью этой статьи было поделиться подходом к использованию привязок ReasonML-JS через реагирующие компоненты. Это делает код очень масштабируемым и структурированным, что было продемонстрировано с помощью примера использования с картами. Хотя привязки и компоненты, представленные здесь, являются наиболее базовыми по своей природе и были предназначены для того, чтобы подчеркнуть эту концепцию, при тщательном проектировании они могут масштабироваться для плавной обработки сложных вариантов использования.