Сообщество 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 &copy;
            <a href='https://www.thunderforest.com/'>
              Thunderforest
            </a>
            , Data &copy;
            <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 через реагирующие компоненты. Это делает код очень масштабируемым и структурированным, что было продемонстрировано с помощью примера использования с картами. Хотя привязки и компоненты, представленные здесь, являются наиболее базовыми по своей природе и были предназначены для того, чтобы подчеркнуть эту концепцию, при тщательном проектировании они могут масштабироваться для плавной обработки сложных вариантов использования.