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

Так что же такое Reason и BuckleScript? Подробный ответ на этот вопрос можно найти на сайте Reason; однако я резюмирую это для целей этой статьи. Reason - это новый синтаксис и набор инструментов для OCaml, разработанный Facebook. Синтаксис и рабочий процесс хорошо знакомы тем, кто работает с JavaScript, поэтому начало работы кажется очень знакомым. В качестве бонуса он также имеет первоклассную поддержку React через ReactReason (примечание - я не буду говорить о ReactReason в этом посте). BuckleScript - это партнерский проект, который позволяет скомпилировать Reason (или OCaml) в читаемый JavaScript. Reason, как и OCaml, имеет статическую типизацию (на самом деле он обеспечивает 100% охват типов) и работает очень быстро (правда, посмотрите).

Запуск нового проекта

Начать работу с Reason очень просто. Сначала пройдите инструкции по настройке вашего редактора на сайте Reason. Это критически важно для вашего успеха с Reason, поскольку плагины редактора могут быть спасением (особенно для тех, кто плохо знаком с языком).

Предполагая, что ваш редактор настроен, давайте установим BuckleScript, выполнив следующее:

npm install -g bs-platform

Тогда приступим к новому проекту. Я назову свой разум-аккордеон, потому что в конечном итоге это то, что я буду делать.

bsb -init reason-accordion -theme basic-reason

Если вы cd в каталог «причина-аккордеон», вы можете запустить npm start, чтобы начать работу. После запуска вы заметите, что BuckleSript на самом деле отслеживает изменения файлов в каталоге src. Все это настраивается через bsconfig.json. Для ясности я переименовал свой файл demo.re в accordion.re. Затем создайте index.html в корневом каталоге (для простоты) со следующим содержимым:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Accordion</title>
</head>
<body>
  <script src="src/accordion.bs.js"></script>
</body>
</html>

Когда мы запустим npm start, BuckleScript выведет для нас файл accordion.bs.js. Позже в этой статье я буду использовать привязки web-api для Reason. Это добавит операторы require в наш JS-файл. Итак, у меня есть несколько вариантов, самый простой из которых - использовать посылку. Parcel будет обрабатывать эти запросы и экспортировать заявления без каких-либо конфигураций. Вот базовая настройка:

npm i -g parcel
npm start 
parcel index.html

Теперь вы сможете перейти на localhost: 1234 и увидеть пустой документ. В консоли вы должны увидеть «Hello, BuckleScript and Reason!» зарегистрирован. Чтобы завершить настройку, установите веб-привязки для Reason.

npm i bs-webapi

В bsconfig.json замените следующую строку:

"bs-dependencies": ["bs-webapi"]

Теперь мы можем использовать веб-привязки в нашем коде Reason. Никаких других конфигураций или настроек не требуется.

Начиная с разума

В оставшейся части этого поста я буду просто возиться с файлом accordion.re. Когда мы вносим изменения в этот файл, BuckleScript соответствующим образом изменяет файл accordion.bs.js. Поскольку BuckleScript выводит JS, который можно читать, может быть полезно время от времени видеть, что BuckleScript выводит для данного кода Reason. Прежде чем перейти к веб-API, я рассмотрю простой синтаксис, который нам понадобится для создания аккордеона. Важно отметить, что точка с запятой в конце строки не является обязательной в Reason.

Пусть привязка

Привязка let позволяет нам присваивать переменные. Подобно const и let в JavaScript, он имеет блочную область видимости и, как и const привязки, неизменяемы. Вот простой пример:

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

Функции

По замыслу Reason - это функциональный язык. Синтаксически функции похожи на стрелочные функции в JavaScript. Вот простой пример:

Редактор мне говорит ('a) => unit. Итак, что такое unit и что означает 'a? 'a - это просто еще один способ сказать, что функция может принимать любой тип. Мы не определяли тип, который logMessage принимает, вместо этого он неявно определен из Js.log (если вы наведете курсор на log в своем редакторе, вы заметите, что он имеет то же определение). Если мы хотим, чтобы logMessage принимал только тип строки, мы можем изменить определение на let logMessage = (str: string) => Js.log(str);. Теперь у Js.log и logMessage разные подписи. Если вы попытаетесь позвонить logMessage(1);, произойдут две вещи. Во-первых, и BuckleScript, и ваш редактор сообщат вам об обнаружении ошибки. В сообщении говорится, что logMessage был вызван с int, но ожидаемой строкой. Во-вторых, BuckleScript не будет компилироваться в JS. Как только вы исправите ошибку, BuckleScript выведет новый JS.

Еще кое-что о функциях: каррирование бесплатное. Фактически каждая функция в Reason каррирована. Вот пример с простой функцией сложения:

Примечание. Наш редактор сообщает нам, что это подпись (int, int) => int. Неявная типизация чрезвычайно эффективна. Я не определял тип x или y, но в зависимости от функции он по-прежнему строго типизирован. Reason всегда имеет 100% -ное покрытие типов, но это не означает, что вам нужно определять каждый отдельный тип.

Очевидно, мы можем позвонить addNumbers(5, 3) и получить 8; однако мы можем назвать это по-другому. Например, добавьте Js.log(addNumbers(5));. В консоли браузера вы увидите function (param) { return 5 + param | 0; }. В этом сила автоматического каррирования! Итак, давайте рассмотрим другие способы вызова addNumbers:

Подождите, а что за оператор конвейера? Оператор конвейера позволяет записывать g(f(x) как x |> f |> g, что является мощным и элегантным способом использования каррирования Reason.

Примечание. Оператор конвейера фактически доступен в JavaScript (если вы используете Babel Stage 1). См. Https://github.com/tc39/proposal-pipeline-operator.

Варианты

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

Я собираюсь написать простой вариант, имея в виду наш пример с аккордеоном. Нашему файлу Reason уже известно о нашей зависимости webapi (просто начните вводить Webapi, и вы увидите, что он начинает автозаполнение); однако, чтобы упростить процесс его использования в этом руководстве, мы собираемся использовать open. open позволяет нам использовать API, не добавляя к каждому объявлению префикса Webapi.Dom. {Независимо} каждый раз, когда мы его используем. Я просто напишу open Webapi.Dom; в верхней части нашего файла.

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

Я определил тип действия с двумя вариантами Expand и Collapse. Оба варианта принимают Dom.element (т. Е. Элемент DOM, для которого мы будем устанавливать максимальную высоту) и string (т. Е. Максимальную высоту варианта). Обратите внимание, что я могу концептуализировать большую часть своего приложения с помощью типов без написания фактического кода.

Изготовление аккордеона

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

toggle - это функция, которая принимает action и возвращает unit. Этот шаблон соответствует варианту в действии. switch - это то, как мы сопоставляем шаблон. Это похоже на другие операторы switch на других языках. Если вы исключите действие Exclude или Collapse в операторе switch, Reason предупредит вас, что оператор switch не является исчерпывающим. Каждый оператор case выполняет две функции: устанавливает атрибут стиля для переданного элемента, а также добавляет и удаляет некоторые классы. Здесь есть несколько пользовательских функций getClassList и toggleClasses.

Обратите внимание на то, как мы используем оператор конвейера, и на то, что все функции каррированы в Reason. Это приводит к элегантному читаемому коду. Чтобы увидеть, как это работает, наведите указатель мыши на Element.classList и обратите внимание, что это функция. Именно так в Reason будет работать большинство API. Если вы работаете с JavaScript, вам придется привыкнуть к тому факту, что почти все в Reason является функцией. Element.classList - это функция, которая принимает Dom.element. Я могу увидеть это в своем редакторе, наведя курсор на classList. Давайте перепишем последнюю строку каждого случая, чтобы лучше понять, что происходит:

Это относительно простое объединение функций в цепочку. Представьте, если бы у нас была гораздо более длинная композиция. Используя |>, легко увидеть, как эти функции работают вместе.

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

В оставшейся части примера мы представим, что у нас есть следующая структура HTML в нашем файле index.html:

...
<body>
<div class="accordion">
  <div class="section">
    <div class="header"></div>
    <div class="content"></div>
  </div>
  <div class="section">
    <div class="header"></div>
    <div class="content"></div>
  </div>
</div>
</body>
...

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

Давайте сначала выберем наш аккордеон в DOM:

Я также определю unwrapElement:

Что это? Что ж, вот еще кое-что о Reason: система шрифтов на 100% надежна. Это означает, что если x имеет тип int, x никогда не будет каким-либо другим типом, включая null. Это означает, что чистая программа Reason гарантирует, что у вас никогда не будет нулевой ошибки!

Возвращаясь к приведенному выше примеру, Document.querySelector фактически возвращает option(Dom.element). option - это встроенный вариант с двумя регистрами Some('a) и None. Вероятно, вы захотите обработать каждый случай более элегантно, чем я сделал выше (случай None просто возвращает ошибку), но для целей этого примера это нормально. Обратите внимание, что каждый случай в любом варианте должен возвращать один и тот же тип. raise возвращает 'a, который относится к любому типу, и поэтому вышеуказанное работает.

Теперь давайте выберем нашу часть аккордеона:

Обратите внимание, что Element.querySelectorAll возвращает Dom.nodeList, поэтому разворачивать его не нужно. Используя разделы, выбранные выше, мы можем создать настоящую внутренность приложения.

На высоком уровне давайте разберемся, что мы здесь делаем.

  1. Мы превращаем наш NodeList в массив.
  2. Мы отображаем наш массив с помощью composeItem. composeItem возвращает нам массив элементов в нашем аккордеоне.

В приведенной выше функции отсутствуют несколько типов и методов. Я добавил это:

Вот объяснение приведенного выше фрагмента кода.

  1. asDomElement использует взаимодействие Reason для преобразования (приведения) любого типа в элемент DOM. Пер Гленн о разногласиях Reason, DOM настолько динамично типизирован, что невозможно определить настоящий тип. В результате мы прибегаем к использованию asDomElement, и в данном конкретном случае в этом нет ничего плохого.
  2. accordionItem - это рекорд. Запись похожа на объект JavaScript, за исключением того, что она неизменяема и имеет фиксированные имена и тип полей. Если вы посмотрите на подпись composeItem, она фактически вернет accordionItem. Reason смог «рассудить», что последняя часть composeItem на самом деле была accordionItem, основываясь исключительно на композиции.
  3. getSectionElement и getHeight - простые служебные функции, которые довольно просты.
  4. handleHeaderClick - наш обработчик событий. Обратите внимание, как composeItem добавляет этот обработчик событий с addClickEventListener. Есть более общий addEventListener, но у него есть более общий Dom.event тип, который затем необходимо преобразовать в Dom.mouseEvent. Webapi от Reason упрощает нам задачу.
    Примечание. Подробнее об этой проблеме можно прочитать здесь.

Завершение этого

Приведенный выше код помог нам создать простой аккордеон (учитывая, что вы добавили немного CSS для перехода к вашему index.html). Вот скриншот последнего примера.

Надеюсь, этот пост поможет вам начать работу с Reason. Хорошим предшественником Reason является хорошее понимание функционального программирования и связанных с ним шаблонов. Если вы работаете с фоном JavaScript, Functional Light от Kyle - хорошее место для начала. Документация по причинам также является отличным ресурсом с множеством примеров, которые помогут вам на этом пути. У Reason огромное, растущее сообщество, и сейчас весело быть частью такого нового языка. Вы можете присоединиться к беседе на канале Reason Discord.

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