Функциональное программирование внешнего интерфейса с помощью 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
. Это неплохое начало, если я сам так говорю. На следующем уроке вы узнаете, как играть в последовательность и заставлять пользователя взаимодействовать с игрой на экране.
Если вам нравится то, что я написал, подумайте о том, чтобы купить мне кофе.