TL;DR: в этих сериях вы узнаете, как заставить React и Redux управлять SVG элементами для создания игры. Полученные в этой серии знания позволят вам создавать анимацию не только для игр. Вы можете найти окончательный вариант исходного кода, разработанного в этой части, на GitHub.   Название игры: "Пришельцы, проваливайте домой!" Игра, разработкой которой вы займетесь в этой серии, называется "Пришельцы, проваливайте домой!". Идея игры проста: у вас будет пушка, с помощью которой вы будете сбивать "летающие диски", которые пытаются вторгнуться на Землю. Для уничтожения этих НЛО вам нужно произвести выстрел из пушки, наведя курсор и кликнув мышью. Если вам интересно, можете найти и запустить итоговую версию игры здесь (ссылка умерла — прим.переводчика). Но не увлекайтесь игрой, у вас есть работа! Для успешного ознакомления со статьей вам необходимо иметь определенный уровень знаний о вэб-разработке (главным образом о JavaScript) и компьютер с предустановленными Node.js и NPM. Вам не пригодятся глубокие знания JavaScript, React, Redux и SVG для успешного прохождения этой серии туториалов. Впрочем, если вы в теме, вам будет проще понять некоторые части и их соответствие друг другу. Тем не менее, в серии содержатся ссылки на соответствующие статьи, посты и документы, которые помогут лучше объяснить темы, заслуживающие повышенного внимания. Хотя в предыдущем разделе не было упоминаний о Git, стоит отметить, что это хороший инструмент для решения некоторых проблем. Все профессиональные разработчики используют Git (либо иную систему управления версиями, например, Mercurial или SVN) в процессе своей деятельности, даже в ходе "домашних" проектов. Зачем создавать проект без резервного копирования? Вам даже не нужно за это платить. Вы можете воспользоваться такими сервисами, как GitHub (лучший!) или BitBucket (неплох, если быть честным) и сохранить ваш код в надежных облачных инфраструктурах. Использование подобных инструментов даст вам уверенность в сохранности вашего кода и облегчит непосредственно процесс разработки. Например, когда вы создадите новую версию приложения с "багами", вы легко сможете вернуться к предыдущей версии кода, совершив всего несколько простых действий используя Git. Другим значительным преимуществом является возможность следить за каждым разделом в этой серии и коммитить код, разработанный поэтапно. Это легко позволит вам видеть изменения в коде после выполнения каждой секции. Сделайте вашу жизнь легче уже сейчас, пока вы проходите этот туториал. В общем, сделайте себе одолжение и установите Git. Также создайте учетную запись на GitHub (если у вас ее еще нет) и репозиторий для сохранения вашего проекта. Затем, после завершения каждой секции, коммитьте изменения в репозиторий. Ох, и не забывайте пушить ваши изменения. Самый первый ваш шаг для создания игры с React, Redux и SVG — использование create-react-app для быстрого старта вашего проекта. Как вы, вероятно, уже знаете (ничего страшного, если это не так), create-react-app — это инструмент с открытым исходным кодом, поддерживаемый Facebook, который помогает разработчикам мгновенно начать работу с React. Имея установленные Node.js and NPM (версия последнего должна быть 5.2 и выше), вы можете использовать create-react-app, даже не устанавливая его: Эта "тулза" создаст стуктуру, похожую на ту, что приведена ниже: Инструмент create-react-app популярен, хорошо описан в документации и имеет хорошую поддержку в коммьюнити. Если вам интересно углубиться в детали, вы можете проверить репозиторий create-react-app на гитхабе и ознакомиться с руководствами пользователя. Прямо сейчас можно избавиться от перечисленных ниже файлов, потому что в дальнейшем они нам не пригодятся: Удаление этих файлов может привести к ошибке в случае, если вы попытаетесь запустить проект. Это легко исправить, удалив два "импорта" из файла ./src/App.js: А также путем рефакторинга метода render(): После разворачивания проекта и удаления из него ненужных файлов следует настроить Redux как единственный верный источник данных в вашем приложении. Также следует установить PropTypes, это поможет избежать некоторых распространенных ошибок. Оба инструмента могут быть установлены с помощью одной команды: Как видите, вышеописанная команда включает третий NPM пакет: react-redux. Не рекомендуется использовать Redux напрямую с React. Пакет react-redux выполняет некоторые громоздкие для ручной обработки оптимизации производительности. С помощью описанных пакетов вы можете настроить ваше приложение для использования Redux. Это несложно, вам лишь нужно создать контейнер (умный компонент), презентационный компонент (глупый компонент) и редьюсер. Разница между умным и глупым компонентами в том, что первый просто соединяет (connect) глупые компоненты с Redux. Третий создаваемый вами элемент, редьюсер, является основным компонентом в Redux store. Этот компонент отвечает за выполнение "экшенов" (действий), вызываемых различными событиями в вашем приложении, и за применение функций для изменения "стора" (источника данных) на основе этих действий. Удобнее начинать процесс с создания редьюсера, так как этот элемент не зависит от других (на самом деле, все наоборот). Для сохранения структурированности можно создать новый каталог под названием reducers, внутри него разместить src и добавить туда файл с именем index.js. Этот файл может содержать следующий исходный код: Таким образом ваш редьюсер просто инициализирует состояние приложения сообщением (message), что React и Redux легко интегрируются. Вскоре в этом файле вы начнете определять действия и обрабатывать их. Затем вы можете провести рефакторинг компонента App чтобы вывести это сообщение пользователям. Самое время воспользоваться PropTypes. Для этого нужно открыть файл ./src/App.js и заменить его содержимое следующим текстом: Как видите, с использованием PropTypes очень просто определить типы, которые ожидает ваш компонент. Вам нужно лишь установить PropTypes свойства компонента App с необходимыми параметрами. В сети есть шпаргалки (например, вот эта, эта и эта), в которых описаны способы создания базовых и расширенных PropTypes определений. Ознакомьтесь с ними, если нужно. После определения исходного состояния вашего store (стора) и того, что ваш компонент App должен отображать, вам необходимо связать эти элементы вместе. Именно для этого необходимы контейнеры. Для создания контейнера в нашей структуре вам нужно создать каталог с именем сontainers внутри каталога src. После этого в новом каталоге создайте компонент с названием Game внутри файла Game.js. Этот контейнер будет использовать функцию connect из react-redux для передачи state.message к параметрам сообщения компонента App: Переходим к финальной стадии. Последним шагом к тому, чтобы связать все вместе будет рефакторинг файла ./src/index.js для инициализации Redux хранилища (store) и передачи его в контейнер Game (который затем получит сообщение и пошлет (прокинет) их в App). Следующий код показывает, как будет выглядеть файл ./src/index.js после рефакторинга: Вы это сделали! Чтобы оценить, как все работает, в терминале перейдите в корень проекта и выполните npm start. Таким образом вы запустите приложение в режиме разработки (в dev-режиме), и оно откроется в установленном по умолчанию браузере. В этой серии вы оцените простоту создания SVG компонентов с использованием React. На самом деле практически нет разницы между созданием компонентов React с HTML или с SVG. Основным отличием является то, что в SVG вводятся новые элементы и эти элементы рисуются на холсте SVG. Прежде чем приступить к созданию собственных компонентов с SVG и React, будет полезно вкратце ознакомиться с SVG. SVG — один из самых крутых и гибких веб-стандартов. SVG, что расшифровывается как Scalable Vector Graphics (масштабируемая векторная графика), представляет собой язык разметки, позволяющий разработчикам описывать двухмерную векторную графику. Язык SVG довольно похож на HTML. И тот, и другой являются языками разметки на основе XML и отлично работают совместно с другими вэб-стандартами, такими как CSS и DOM. Из этого следует, что правила CSS одинаково применимы как к SVG, так и к HTML, включая анимацию. На протяжении этой серии с помощью React вами будут созданы более десятка компонентов SVG. Придется даже составлять (группировать) SVG элементы для формирования игровых объектов (пушка, стреляющая пушечными ядрами). Более подробное изучение SVG невозможно в рамках нашей статьи, оно сделает ее слишком длинной. Если у вас есть желание лучше изучить язык разметки SVG, вы можете ознакомиться с учебником, представленным Mozilla, а также с изложенным в этой статье материалом о системе координат SVG. Однако, прежде чем приступить к созданию собственных компонентов, важно усвоить несколько характеристик SVG. Во-первых, использование SVG в сочетании с DOM позволяет разработчикам "делать вещи". Использовать SVG с React очень просто. Во-вторых, система координат SVG похожа на декартову плоскость, перевернутую вверх ногами. Соответственно, по умолчанию отрицательные значения по вертикали будут отображены выше оси Х. При этом горизонтальные значения расположены так же, как и в декартовой плоскости, то есть отрицательные значения находятся слева от оси Y. Такое поведение можно легко изменить, применив преобразование к холсту SVG. Однако будет лучше придерживаться параметров по умолчанию, дабы избежать путаницы среди разработчиков. Вы быстро привыкнете. И наконец, следует помнить, что в SVG представлено множество новых элементов (таких, как circle, rect и path). Для использования этих элементов недостаточно просто определить их внутри HTML элемента. Сперва вам нужно определить svg элемент (ваш холст), где вы нарисуете все ваши SVG компоненты. Рисование элементов SVG может быть выполнено тремя способами. Во-первых, можно использовать базовые элементы, такие как rect, circle и line. Однако эти элементы не обладают особой гибкостью. Они позволяют вам рисовать простые фигуры в соответствии с их названием (прямоугольник, круг и линию). Второй способ заключается в комбинировании базовых элементов для получения более сложных форм. Например, можно использовать прямоугольник (rect) с одинаковыми сторонами (получится квадрат) и две линии для того, чтобы нарисовать дом. Впрочем, такой подход все равно довольно сильно ограничивает. Третий и наиболее гибкий способ — применять path элементы. Такой вариант позволяет разработчикам создавать довольно сложные формы. Рисование фигуры происходит посредством указания определенных команд браузеру. Например, чтобы нарисовать "L", вы можете создать path элемент, который содержит три команды: Path элементы принимают множество других команд. Одна из важнейших — команда кубических кривых Безье. Она позволяет вам добавить любые "сглаженные" кривые с применением двух опорных точек и двух контрольных точек. Например, чтобы нарисовать "U", выполните следующее: В таком случае команды, переданные path элементу, указывают браузеру: Не отчаивайтесь, если вы до сих пор не поняли принцип работы кубических кривых Безье. У вас будет возможность попрактиковаться в этой серии. Кроме того, вы можете найти множество руководств об этой функции в Интернете, где всегда можно практиковаться в таких инструментах, как JSFiddle и Codepen. (речь идет не о тэге <canvas></canvas>, а о react-компоненте Canvas (по-русски Холст) — прим.переводчика) После того как вы создали структуру своего проекта и узнали основы SVG, можно приступать к созданию игры. Первый элемент, который необходимо создать — это холст SVG, который используется для рисования элементов игры. Этот компонент характеризуется как презентационный (глупый). Таким образом, вы можете создать каталог components, внутри каталога ./src для хранения нового компонента и его "братьев и сестер" (соседних/дочерних элементов — прим.переводчика). Поскольку это будет ваш холст, сложно придумать для него более естественного названия, чем Canvas. Создайте новый файл с именем Canvas.jsx внутри каталога ./src/components/ и добавьте следующий код: Перепишите компонент App, так чтобы он использовал компонент Canvas: Если вы запустите проект (npm start) и проверите ваше приложение, вы увидите, что браузер рисует только четверть этого круга. Это происходит из-за того, что по умолчанию точка начала координат расположена в левом верхнем углу экрана. Кроме того, вы увидите, что svg элемент не занимает весь экран. Для более интересного и удобного управления сделайте свой холст (<Canvas/>) пригодным для рисования на весь экран. Можете переместить его начало в центр оси Х и сделать ближе к низу (позже вы добавите к оригиналу свою пушку). Для выполнения обоих условий вам нужно изменить два файла: ./src/components/Canvas.jsx и ./src/index.css. Можете начать с замены содержимого Canvas, применим следующий код: В этой версии компонента вами был задан атрибут viewBox для тэга svg. Этот атрибут позволяет определить, что ваш холст и его содержимое должны соответствовать определенному контейнеру (в данном случае — внутренней области окна/браузера). Как вы видите, атрибут viewBox состоит из четырех чисел: Помимо определения атрибута viewBox в новой версии также задается атрибут под названием preserveAspectRatio. Вы использовали xMaxYMax none на нем для принудительного равномерного масштабирования вашего холста и его элементов. (у меня установка preserveAspectRatio вызывала предупреждение от react — прим.переводчика) После рефакторинга вашего холста вам необходимо добавить следующее правило в файл ./src/index.css: Это делается для того, чтобы элементы (тэги) html и body скрыли (и отключили) прокрутку. Кроме того, элементы будут отображены на весь экран. Если вы проверите приложение прямо сейчас, обнаружите, что круг расположен внизу экрана в центре по горизонтали. После того, как на вашем холсте будет установлено разрешение на весь экран и начало координат будет размещено в его центре, можно приступить к созданию реальных игровых элементов. Начать можно с оформления фонового элемента для вашей игры — неба. Для этого создайте новый файл под названием Sky.jsx в каталоге ./src/components/ применив следующий код: Вам может быть интересно, почему для игры задается настолько большая площадь (ширина 5000 и высота 1200). В действительности, ширина в этой игре не имеет значения. Вам лишь нужно задать ее числом, достаточным для того, чтобы покрыть любой размер экрана.  В свою очередь, важна высота. Поскольку вы установили 1200 точек на холсте независимо от разрешения и ориентации экрана пользователя, это даст вашей игре согласованность и все пользователи будут видеть одинаковую область. Таким образом, вы сможете определить, где появятся летающие диски и как долго они будут проходить через заданные точки. Чтобы на холсте отображалось небо (компонент Sky), откройте файл Canvas.jsx в своем редакторе и исправьте его так: Если сейчас вы проверите приложение (npm start), то вы увидите, что круг по-прежнему находится по центру внизу, а ваш фон стал синим. Примечание: если вы добавите элемент Sky после элемента circle, вы больше не увидите ваш круг. Это объясняется тем, что SVG не поддерживает z-index. SVG определяет, который из элементов находится "выше", в соответствии с порядком их перечисления. То есть вам необходимо описать элемент circle после Sky, чтобы веб-браузеры отображали его поверх синего фона. После создания компонента Sky следует перейти к созданию компонента Ground. Для этого создайте новый файл Ground.jsx в каталоге ./src/components/ и добавьте следующий код: В этом элементе нет ничего фантастического. Это всего лишь композиция элемента rect (прямоугольник) и line (линии). Однако, как вы заметили, этот элемент также содержит константу со значением 5000, определяющую его ширину. Поэтому было бы неплохо создать файл для сохранения глобальных констант, подобных этой. Раз уж мы пришли к такому выводу, создайте новый каталог с именем utils внутри каталога ./src/ и внутри этого нового каталога создайте файл с именем constants.js. На данный момент у нас всего одна константа для хранения в этом файле: После этого вы можете отрефакторить оба компонента и Sky и Ground, чтобы в них использовалась эта константа. Не забудьте добавить компонент Ground к вашему холсту (помните, что вам нужно разместить его между небом и элементом окружности (т.е. между Sky и circle)). Если у вас имеются какие-либо сомнения касательно последних шагов, пожалуйста, взгляните на этот коммит. В вашей игре уже созданы элементы, которые определяют небо и землю. В качестве следующего шага хочется добавить нечто более интересное. Возможно, стоит добавить компоненты, которые нарисуют вам пушку. Эти элементы будут немного сложнее описанных ранее элементов. Они содержат намного больше строк исходного кода, но это обусловлено тем, что для их рисования необходимо использовать кубические кривые Безье. Как вы помните, кубические кривые Безье определяются четырьмя точками: начальной, конечной и двумя контрольными. Эти точки, заданные в свойстве d элемента path, выглядят следующим образом: M 20 20 C 20 110, 110 110, 110 20. Чтобы в ходе создания кривых в вашем коде не было повторения подобных шаблонов, рекомендуется создать новый файл с именем formula.js в каталоге ./src/utils/ и добавить функцию, которая будет возвращать эту строку на основе некоторых параметров: Это достаточно простой код, он просто извлекает (деструктурирует) четыре атрибута (initialAxis, initialControlPoint, endControlPoint, endAxis) из аргумента функции под названием cubicBezierCurve и передает их в шаблонный литерал, который построит необходимую кубическую кривую Безье. Используя этот файл, вы можете начать создавать вашу пушку. Для поддержания структуры можно условно разделить пушку на две части: CannonBase (база) и CannonPipe (дуло). Для описания CannonBase создайте новый файл CannonBase.jsx внутри ./src/components и добавьте в него следующий код: За исключением кубической кривой Безье в этом элементе нет ничего нового. В итоге браузер отобразит этот элемент как кривую с темно-коричневой (#75450e) границей и "зальет" ее площадь светло-коричневым цветом (#a16012). Код для создания CannonPipe будет похож на код CannonBase. Отличия заключаются в других цветах и в том, что он передает другие точки в формулу pathFromBezierCurve, чтобы нарисовать трубу. Кроме того, этот элемент использует атрибут transform для имитации вращения пушки. Для создания этого элемента добавьте следующий код в новый файл CannonPipe.jsx внутри каталога ./src/components/: После этого сотрите элемент окружности с вашего холста и добавьте к нему CannonBase и CannonPipe. После рефакторинга холста вы получите следующий код: Запуск и проверка приложения отобразит вам следующую векторную графику:  Наш процесс набирает обороты! Вы уже создали фоновые элементы (Sky и Ground) и свою пушку (CannonBase + CannonPipe). На данном этапе проблема заключается в отсутствии анимации. Следовательно, чтобы сделать что-то интересное, можно сосредоточиться на создании прицела. Для этого вы можете добавить слушатель событий onmousemove к вашему холсту и настроить его обновление при каждом возникновении события, т.е. каждый раз, когда пользователь двигает мышью, однако это скажется на производительности вашей игры. Во избежание этой ситуации, вы можете установить интервал, по которому будете проверять последнюю позицию мыши для обновления угла наклона вашего элемента CannonPipe. В этом случае вы используете слушатель onmousemove с той лишь разницей, что эти события не будут вызывать частых ре-рендеров (перерисовок) всего холста. Будет обновляться только нужное свойство в вашей игре (угол наклона пушки), а заданный интервал будет использовать это свойство для запуска повторной обработки (путем обновления хранилища Redux). В этом случае вам впервые потребуется использование Redux action (экшн, действие) для обновления состояния вашего приложения (а если быть точнее — угла наклона вашей пушки). Исходя из этого, вы создаете каталог с именем Actions внутри каталога ./src/. В этом новом каталоге вам нужно будет создать файл под названием index.js, содержащий следующий код: Примечание: назовите этот экшн MOVE_OBJECTS, поскольку он будет использоваться не только для обновления пушки. В следующих частях этой серии говорится о применении этого экшна для анимации пушечных шаров и летающих объектов. После определения этого действия необходимо поправить редъюсер (файл index.js внутри ./src/reducers/):  В новой версии этого файла проверяется экшн, и, если он имеет тип MOVE_OBJECTS, вызывается функция moveObjects. Вам все же следует задать эту функцию, но обратите внимание, что данная версия также определяет начальное состояние (initial state) вашего приложения, содержащее свойство под названием angle со значением 45. Именно под таким углом будет нацелена ваша пушка после запуска приложения. Как вы уже заметили, функция moveObjects также является редъюсером. Для того, чтобы сохранить проект в должной мере поддерживаемым и структурированным, рекомендуется сохранить эту функцию в новом файле, поскольку в вашей игре будет присутствовать достаточное количество редъюсеров. Поэтому создайте файл moveObjects.js внутри ./src/reducers/ и добавьте к нему следующий код: Этот код достаточно прост, он лишь извлекает свойства x и y из mousePosition и передает их функции calculateAngle для получения нового угла наклона. Затем, наконец, он генерирует новое состояние (новый объект) с новым значением угла. Вероятно, вы заметили, что не определили функцию calculateAngle в файле formula.js, верно? Изучение математического выражения для расчета угла наклона, основанного на двух точках, выходит за рамки данной серии, но если вам интересно, пройдите по ссылке на StackExchange, чтобы понять, как это происходит. Наконец, вам нужно добавить следующие функции в файл formula.js (./src/utils/formulas): Примечание: функция atan, выполняемая объектом Math, возвращает результат в радианах. Вам нужно преобразовать это значение в градусы. Для этого и применяется функция radiansToDegrees. После того, как были определены новый экшн и новый редъюсер, вам необходимо ими воспользоваться. Поскольку управление состоянием вашей игры основано на Redux, нужно сопоставить action (экшн) moveObjects с props (свойствами) вашего компонента App. Это осуществляется путем рефакторинга контейнера Game. Открывайте файл Game.js (./src/containers) и заменяйте его содержимое следующим: С этим маппингом (mapStateToProps и mapDispatchToProps) вы можете использовать необходимые данные в компоненте App в качестве props. Вам нужно открыть файл App.js (расположенный по адресу ./src/) и заменить его следующим: Обратите внимание, что новая версия значительно отличается. Ниже представлен обобщенный список изменений: Обновите ваш App компонент и добавьте следующую функцию в файл formula.js: Если вам интересно, для чего это понадобилось, загляните на StackOverflow. Чтобы завершить разработку прицела для пушки, вам необходимо обновить последнюю часть кода — Canvas компонент. Откройте файл Canvas.jsx (расположенный в ./src/components) и замените его содержимое следующим: Отличия от прежней версии: Свершилось! Можете взглянуть через прицел на своего врага. Зайдите в терминале в корень проекта и введите npm start (если он еще не запущен). Затем откройте http://localhost:3000/ в веб-браузере и подвигайте мышью. Тем самым вы приведете в движение свою пушку. И как вам, весело?! В первой части этой серии вы познакомились с некоторыми важными темами, знание которых поможет вам в создании полной версии игры. Вы также использовали приложение create-react-app для быстрого старта вашего проекта и создали некоторые игровые элементы, такие как пушка, небо и земля. В конце концов, вы снабдили свою пушку прицелом. Обладая данными элементами, вы можете создать остальные элементы и сделать их анимированными. В следующей статье этой серии вы будете создавать эти элементы, сделаете так, чтобы летающие диски возникали случайным образом в заранее определенных положениях. После этого вы научите свою пушку стрелять ядрами.  Это будет круто! Оставайтесь на связи! Думаю, что иногда полезно отвлечься от "серьезных" проектов. А что думаете вы? Перевод второй части Провожу вебинары, разрабатываю на заказ (react)