Функциональное программирование внешнего интерфейса с помощью Reason и ReasonReact

ОБНОВЛЕНИЕ 02.08.2020: этот пост обновлен до последних версий экосистемы ReasonML и JavaScript.

Создание учебных пособий с использованием Simon Game в качестве вдохновения - все началось с FreeCodeCamp. Но в целом я думаю, что это хорошее начало для кого-то вроде меня, которому нравится создавать умеренно сложные вещи в свободное время. Что мне больше всего нравится в игре Simon Game, так это количество состояний, которые нужно контролировать, и то, что асинхронность играет такую ​​большую роль. Кроме того, это заставляет меня изучать более непонятные API, например Audio, которые я бы иначе не трогал.

Покажи мне код 👀

Код для этой части проекта можно найти здесь 👇



Начало работы 🚀

Чтобы начать локально, ознакомьтесь со статьей, вы можете использовать установку с использованием webpack и bs-platform или ссылку github выше. После этого вы должны быть готовы к следующему этапу.

Установить 📦

Последняя версия reason-react не включает такой же редуктор, как старая. Чтобы уменьшить количество изменений в этой статье по сравнению с тем, когда она была написана в последний раз, установите reason-react-update и добавьте его в свой bs.config.json.

yarn add reason-react-update

Компонент 📦

С помощью reason-react-update вы сможете обновлять состояние так же, как вы делали это с помощью предыдущего reason-react API. Таким образом, вы можете обновить состояние с помощью последовательности, которая создается, когда компонент монтируется, и обновлять его, когда пользователь проигрывает или выигрывает игру.

Полезный ресурс



Над объявлением компонента будут указаны типы состояния. В состоянии будет только одно свойство, и оно будет называться sequence. Сначала вы объявите, что такое последовательность:

type sequence = array(int)

Здесь вы утверждаете, что sequence - это массив целых чисел. Довольно просто. Теперь давайте объявим состояние и действия.

type state = { sequence }
type actions = | SetSequence(sequence)

type state = { sequence } - это сокращение для type state = { sequence: sequence }, поскольку вы объявили sequence выше.

У вас есть одно действие SetSequence(sequence), которое будет использоваться для обновления состояния сгенерированной случайной последовательностью.

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

[@react.component]
let make = () => {
  let (state, send) =
    ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
      /* Wait for it... */
    );

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

let (state, send) =
    ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
      switch (action) {
      | SetSequence(array) => Update({sequence: array})
      }
    );

Когда редуктор получает действие SetSeqeunce с сгенерированным array, состояние будет обновлено с этой последовательностью.

А пока давайте визуализируем sequence, который в данном случае ни черта не даст.

[@react.component]
let make = () => {
  let (state, send) =
    ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
      switch (action) {
      | SetSequence(array) => Update({sequence: array})
      }
    );
<div>
    {state.sequence
     |> Array.map(number =>
          <span key={Js.Math.random() |> Js.Float.toString}>
            {number |> Js.Int.toString |> React.string}
          </span>
        )
     |> React.array}
  </div>;
};

Я использую оператор канала |>, чтобы вставить sequence в качестве последнего аргумента в Array.map. Все функции в Reason выполняются автоматически. Наряду с оператором канала это позволяет вам писать код, который немного легче читать.

Я использую функцию Js.Math.Random BuckleScript для генерации случайного числа от 0 до 1, а затем использую Js.Float.toString BuckleScript для преобразования его в строку. Это будет ключ к элементу. Возможно, лучше использовать более уникальный идентификатор, но пока этого достаточно.



Каждое число в последовательности преобразуется в строку с использованием Js.Int.toString, а затем преобразуется в строку React, чтобы правильно отобразить ее в DOM. Поскольку я отображаю массив целых чисел, мне нужно преобразовать массив в массив React, чтобы обеспечить правильное отображение типа массива.

Как я уже сказал, сейчас ни черта не происходит, просто подожди!

Randos 🔢

Теперь вы находитесь в той точке, где вы действительно хотите отобразить на экране какой-то шум. Вы будете использовать useEffect0, хук эффекта без зависимостей, который запускается при монтировании. Но сначала давайте сгенерируем последовательность чисел от 1 до 4 (включительно).

let array =
  Belt.Array.makeBy(20, _i =>
    Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
  );

Что ты здесь делаешь? 🤷‍♂️ Итак, сначала вы используете новую стандартную библиотеку BuckleScript, известную как Belt. Это дает разработчикам JS удобные утилиты, которые они обычно используют для интерфейсной разработки. В вашем распоряжении метод, который генерирует массив с функцией обратного вызова, преобразующей каждый элемент. Это функция makeBy.



Внутри этой функции вы используете Math библиотеку BuckleScript, ту же, что вы использовали раньше. Однако есть одно небольшое отличие, которое вы, вероятно, заметите, - это синтаксис *.. Теперь я собираюсь сказать вам слово, которое будет звучать круто и умно: специальный полиморфизм.

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



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

Пример

/* Adding Integers */
1 + 1
/* Adding Floats */
1 +. 1

Есть некоторые споры о том, следует ли Окамлу это изменить или нет. А пока вам нужно знать и то, и другое.



Наконец, вы заметите, что у меня есть подчеркивание перед i. _i указывает, что переменная не используется. Компилятор будет кричать на вас, если вы объявите неиспользуемую переменную. Тебе должно быть стыдно!

Что теперь, когда у вас есть рандо? Состояние обновления!

send(SetSequence(array));

Это было не так уж сложно, правда? Вот остальная часть кода для полноты.

React.useEffect0(() => {
    let array =
      Belt.Array.makeBy(20, _i =>
        Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
      );
    send(SetSequence(array));
    None;
  });

Там внизу есть None, потому что этот эффект ожидает возврата Option. Простой None подойдет для удовлетворения этого требования.

Вся Энчилада 🌯

/* App.re */ 
type sequence = array(int);
type state = {sequence};
type action =
  | SetSequence(sequence);
[@react.component]
let make = () => {
  let (state, send) =
    ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
      switch (action) {
      | SetSequence(array) => Update({sequence: array})
      }
    );
React.useEffect0(() => {
    let array =
      Belt.Array.makeBy(20, _i =>
        Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
      );
    send(SetSequence(array));
    None;
  });
<div>
    {state.sequence
     |> Array.map(number =>
          <span key={Js.Math.random() |> Js.Float.toString}>
            {number |> Js.Int.toString |> React.string}
          </span>
        )
     |> React.array}
  </div>;
};

Резюме 📚

В этом уроке вы узнали, как генерировать случайные числа, объявлять типы и обновлять состояние в reason-react. Это неплохое начало, если я сам так говорю. На следующем уроке вы узнаете, как играть в последовательность и заставлять пользователя взаимодействовать с игрой на экране.

Если вам нравится то, что я написал, подумайте о том, чтобы купить мне кофе.

ПРОДОЛЖИТЕ ЧАСТЬ 2