Вы можете найти весь код для этого здесь.

VueJS - это технология, набирающая популярность среди веб-разработчиков. Он объединяет некоторые из лучших аспектов Angular и React в легкий и производительный фреймворк. Из-за этого есть много вещей, которые вы можете делать в Vue быстрее, чем в Angular или React.

Все фреймворки хорошо работают в своей области, поэтому их стоит изучить. В этом уроке мы собираемся изучить Vue, создав игру в крестики-нолики. Мы также собираемся интегрировать firebase, чтобы мы могли играть в нее с другими людьми в Интернете, хотя это не означает, что играть с самим собой тоже не очень весело.

Для кого предназначено это руководство?

Это не введение в HTML, JavaScript или CSS. Вы должны хорошо понимать эти языки. Вам также необходимо иметь опыт использования npm для загрузки пакетов, и было бы очень полезно, если бы вы раньше работали с React или Angular, поскольку многие концепции одинаковы. Вам также необходимо знать, как пользоваться терминалом. Однако вам совсем не обязательно знать Vue. Я все объясню.

С чего начать?

Используя npm, загрузите Vue CLI глобально:

npm i -g @vue/cli

Затем создайте проект, запустив:

vue create <project-name>

Вы можете заменить <project-name> на ., если хотите создать проект в папке, в которой вы уже находитесь.

vue create .

Дайте генератору сделать свою работу, а затем откройте вашу среду IDE в корне проекта. Вы должны увидеть кучу файлов. Не важно знать, что их больше всего. Важные вещи находятся в папке src, и там мы будем делать всю работу.

Vue уже создал несколько файлов в папке src. Большинство из них нам не понадобится, так как мы будем начинать с нуля. Так что продолжайте и удалите папку assets/ и файл components/HelloWorld.vue. Затем откройте App.vue, удалите весь код и замените его следующим:

<template>
  <h1>Tic Tac Toe</h1>
</template>
<script>
export default {
  name: "app"
};
</script>

Я немного объясню это, но вы можете просто вставить это и пока игнорировать.

На этом этапе вам нужно установить плагин Vetur, если вы используете VSCode. Он предоставляет множество хороших инструментов для разработки на Vue. Если вы используете другой редактор, я ничем не могу вам помочь. Обратитесь к врачу.

Откройте терминал в корне вашего проекта и запустите npm run serve. Затем перейдите к localhost:8080 и, если все работает, вы должны увидеть пустую страницу с Tic Tac Toe большими буквами вверху. Поздравляем, теперь вы разработчик Vue. Позвони всем своим друзьям.

Точка входа в Vue

Точка входа Vue находится в main.js и выглядит так.

Вверху этого файла импортируется Vue и наш компонент приложения. Обратите внимание, что приложение имеет расширение .vue. Все компоненты Vue должны иметь это расширение.

Следующая строка отключает надоедливый совет по производству, который был добавлен только потому, что у людей, работающих над Vue, не было идей, что делать в этот день.

Последние несколько строк инициализируют приложение. Что такое h, я не знаю. Может быть, это означает how the fuck do i name this variable, never mind, i'll just call it h. Строка $mount прикрепляет приложение к любому элементу, который мы хотим. Передача здесь #app означает, что он будет прикреплен к элементу с идентификатором app.

Это главный файл. Интересный материал. Вы можете сделать больше, но сейчас это не так важно. Настоящее веселье ждет нас в другом месте.

Анатомия компонента Vue

Взгляните на App.

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

Компонент Vue состоит из трех тегов: template, script & style. Эти три тега обозначают различные аспекты компонента. Шаблон - это его HTML, сценарий - это сопровождающий JavaScript, а стиль - это его код CSS.

Между тегами script вы должны экспортировать объект. Vue читает этот объект и предоставляет некоторые подробности об этом компоненте. Сейчас мы даем ему только название, но скоро мы увидим, как работать с методами, реквизитами и хуками жизненного цикла.

Интеграция с Firebase

Чтобы сделать нашу игру в крестики-нолики многопользовательской, нам необходимо интегрировать Firebase. Это стоит сделать сейчас, чтобы мы могли использовать Firebase API по мере необходимости, вместо того, чтобы модифицировать его после того, как весь игровой код будет готов.

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

Создание проекта Firebase

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

Вы должны увидеть кнопку Add Project; щелкните по нему и назовите свой проект как хотите. На следующем экране спрашивается об аналитике - вам не нужно включать ее для этого проекта, но это не имеет значения, если вы это сделаете.

Получение конфигурации проекта

Вы должны увидеть это на главном экране вашего проекта. Если вы не можете этого сделать, вернитесь на главную страницу консоли Firebase и нажмите на название своего проекта. Когда закончите, выберите значок </> и назовите свое приложение. Я позвонил своему tic-tac-toe. Включите хостинг Firebase и зарегистрируйте приложение. После этого вы можете просто щелкать next, пока окно не исчезнет.

Теперь мы можем получить конфигурацию нашего приложения, когда она создана. Это объект JSON с информацией о программе. Это то, что мы будем использовать для подключения к нашей базе данных. Вы можете получить конфигурацию, щелкнув значок шестеренки справа от Project Overview, затем настройки проекта, затем прокрутите вниз вкладку General, пока не увидите Firebase SDK Snippet, а затем щелкните Config. Скопируйте этот объект, он будет выглядеть примерно так:

const firebaseConfig = {
  apiKey: "---",
  authDomain: "---",
  databaseURL: "---",
  projectId: "---",
  storageBucket: "---",
  messagingSenderId: "---",
  appId: "---"
};

Вставьте его в новый файл firebase.config.js в папке src вашего проекта Vue. Добавьте это и в свой gitignore, потому что вы не хотите этого делать.

Затем экспортируйте его, например: export default firebaseConfig;.

Установка Firebase

Вам необходимо установить пакет firebase-tools для инициализации и развертывания вашего проекта. Запустите npm i -g firebase-tools в своем терминале. Это дает вам глобальный доступ к команде firebase.

Теперь runfirebase init и выберите следующие параметры.

Выберите свой проект из списка. Это не ваше приложение. Это оригинальный проект, который вы создали на главном экране Firebase. Мой назывался tic-tac-toe-vue.

Оставьте значение по умолчанию для файла правил базы данных.

Ваш общедоступный каталог должен быть dist, не public.

Скажите yes, чтобы переписать все URL-адреса в index.html (это означает, что наше приложение может быть одностраничным).

Не перезаписывайте index.html, вам не нужно этого делать.

Инициализация Firebase в коде

Для запуска Firebase необходимо настроить код. Обычно я делаю это, создавая файл, который называю inmain.js, хотя на самом деле вы могли бы просто написать код прямо там, если хотите.

Сначала установите firebase в локальные зависимости вашего проекта.

npm i firebase

Затем создайте файл с именем firebase.setup.js в папке src/ и добавьте следующий код:

import { initializeApp } from 'firebase';
import firebaseConfig from './firebase.config';
const app = initializeApp(firebaseConfig);
export default app;

Это устанавливает firebase в нашем проекте и позволяет нам получить доступ к его методам api, таким как те, которые используются для общения с базой данных.

Наконец, просто импортируйте установочный файл в src/main.js.

import './firebase.setup';

Компонент входа

В следующих разделах я представлю полный код компонента как суть, а затем объясню каждую часть по очереди: шаблон, скрипт и стиль. Все компоненты будут храниться в src/components, кроме приложения, которое останется там, где оно есть. Никто не хочет тусоваться с приложением.

Сначала мы рассмотрим компонент входа в систему.

Компонент входа в систему не делает ничего особенного. Мы просто позволяем пользователю ввести имя и нажать кнопку для входа в систему. Это имя затем сохраняется в базе данных как «пользователь» и извлекается, если это имя когда-либо вводится снова. Нет пароля или чего-то еще. К чёрту безопасность. Вот код.

Шаблон

Сначала у нас есть закрывающие теги шаблона, которые необходимы в любом компоненте Vue. Как и в React, нам также нужен единственный родительский элемент, для этого и нужен div. Тогда у нас есть две вещи, которых мы не видели.

Первая - это директива v-model. Вот как вы привязываете значение к элементу ввода. username, на который мы здесь ссылаемся, на самом деле является свойством в состоянии компонента Login. Каждый раз, когда мы вводим данные во входные данные, это свойство обновляется, чтобы отразить изменения. Это все, что делает v-model. Как будто атрибуты value и onChange в React родили ребенка, и этот ребенок вырос и стал Райаном Рейнольдсом (сексуальным и крутым).

Второй - v-on:click. Таким образом вы добавляете обработчик кликов, и login ссылается на функцию в компоненте входа в систему, которая должна вызываться при каждом нажатии кнопки.

Скрипт

Это объект, который должны экспортировать все компоненты Vue. У него есть имя для нашего компонента, data функция, которая определяет состояние компонента (в данном случае у нас только одно поле: username), и объект methods, который содержит набор функций, на которые мы можем ссылаться из нашего шаблона. Здесь у нас есть функция login, которую мы прикрепили к обработчику кликов выше. Эта функция проверяет, не является ли поле состояния username пустым, и, если это правда, генерирует событие с именем «логин» и передает поле имени пользователя. Подобные события - это способ передачи данных родительским компонентам, как функция-обработчик в React. Мы перехватим это в App и что-нибудь предпримем, когда это произойдет. В последнюю очередь мы посмотрим на приложение, чтобы увидеть, как все сочетается.

Компонент SelectGame

SelectGame - это компонент, который будет обрабатывать… выбор игры. Действительно? Тебе нужно было это сказать? Вот код.

Шаблон

Здесь нет ничего нового. Шаблон отображает два раздела; один с входом и кнопкой для создания новой игры; и один с входом и кнопкой для присоединения к существующей игре.

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

Скрипт

Здесь мало что происходит. Мы определяем два поля состояния для управления нашими полями ввода.

Стиль

Это не что иное, как базовый CSS. Однако я хочу указать на атрибут scoped в теге style. Это означает, что стили внутри этого компонента будут только применяться внутри этого компонента. Так что вам не нужно использовать селекторы или что-то еще. Вы можете просто стилизовать теги. Ух ты.

Компонент Gameboard

Это последний компонент, на который нам нужно обратить внимание перед App. Он немного больше других, но демонстрирует многие важные аспекты Vue. Он отображает доску на основе следующей структуры данных.

[
  {
    id: 'top-row',
    tiles: [
      { value: '', pos: '0,0' },
      { value: '', pos: '1,0' },
      { value: '', pos: '2,0' }
    ]
  },
  {
    id: 'middle-row',
    tiles: [
      { value: '', pos: '0,1' },
      { value: '', pos: '1,1' },
      { value: '', pos: '2,1' }
    ]
  },
  {
    id: 'bottom-row',
    tiles: [
      { value: '', pos: '0,2' },
      { value: '', pos: '1,2' },
      { value: '', pos: '2,2' }
    ]
  }
];

Каждый объект в массиве представляет собой строку, а каждый объект в каждом массиве tiles представляет одну плитку на нашей доске для игры в крестики-нолики. Поле value содержит информацию о том, какая единица занимает этот тайл (x или o), а поле pos является уникальным идентификатором для каждого тайла.

Вот код самого компонента.

Шаблон

Шаблон состоит из двух разделов. Первый раздел просто отображает gameId, который используется в компоненте SelectGame для присоединения к существующей игре. Второй раздел немного сложнее.

Первая незнакомая вещь в строке 7 - это директива v-if. Эта директива может быть размещена на любом элементе, чтобы контролировать его видимость. По сути, если указанное поле состояния game еще не определено, то раздел не будет отображаться.

В следующей строке 8 используется директива v-for. Это создает список элементов на основе переданного ему выражения. Здесь мы говорим row in game.board, что означает, что для каждого элемента в массиве game.board мы создадим div и все его дочерние элементы. Для каждой итерации данные сохраняются во временной переменной row, к которой мы можем получить доступ в любом месте этого div. Вы можете видеть, что мы используем его в div.tile. Мы должны использовать :key, чтобы помочь Vue отслеживать наш список элементов внутренне. Это то же самое, что и свойство key в списке React.

У нас также есть обработчик кликов в строке 13, который используется для редактирования value плитки при каждом нажатии на нее.

Хотя этот код может немного сбивать с толку (а может и нет), он просто перебирает каждую строку структуры данных выше и создает для нее div. Затем для каждой строки он перебирает каждую плитку и создает для этого div. Тогда наш style блок делает его похожим на доску.

Скрипт

Я возьму тег скрипта по крупицам, так как он довольно большой. Первая строка импортирует database из firebase. Поскольку мы инициализировали наше приложение, мы можем использовать эту функцию для записи непосредственно в базу данных в реальном времени.

import { database } from "firebase";

В следующей части мы определяем некоторые реквизиты. Это похоже на props в react: данные, которые мы можем передать компоненту сверху.

props: ["gameId", "user"]

Затем мы определяем поле состояния game и инициализируем его значением null. Это будет установлено позже, когда мы извлечем игру из базы данных.

data: () => ({
  game: null
}),

Затем у нас есть блок методов, в котором мы определили единственный updateTile метод.

Эта функция ничего не делает во Vue-y, поэтому я просто кратко ее объясню.

В строке 33 мы используем распространение объекта для создания копии нашего поля состояния game.

Затем мы находим правильную строку и плитку для обновления на основе параметров, переданных нам из шаблона, и устанавливаем их значение, равное тому unit, за которое играет наш игрок (x или o).

Наконец, мы вызываем наш метод базы данных. ref здесь соответствует пути в базе данных реального времени. В этом случае у нас есть объект на верхнем уровне нашей базы данных с именем games, и каждый ключ этого объекта - это gameId. Именно так мы храним данные (вы увидите это, когда мы посмотрим на компонент приложения). Итак, все, что мы здесь делаем, это приказываем firebase перейти к объекту games в базе данных, найти игру с нашим идентификатором (gameId) и установить новое значение, которое соответствует нашей обновленной доске.

Последняя часть - это метод created. Это крючок жизненного цикла Vue. Он запускается сразу после инъекции. Вы хотите использовать это для вызовов API и прочего.

В строке 47 мы связываемся с нашей базой данных, используя ту же ссылку, что и выше. Но на этот раз мы ничего не добавляем. Вместо этого мы создаем своего рода веб-сокет. Мы сообщаем Firebase, что каждый раз, когда создается событие value (это происходит при изменении данных), мы хотим вызвать обратный вызов:

snapshot => {
  this.game = snapshot.val();
  if (this.game.creator === this.user.name) {
    this.unit = "x";
  } else {
    this.unit = "o";
  }
}

Это устанавливает для нашего поля состояния game значение, хранящееся в базе данных на момент вызова обратного вызова (snapshot.val()), а также определяет, какой единицей измерения мы должны быть. Если мы создали игру, то мы x. Помните, что этот обратный вызов вызывается каждый раз /game/${gameId} объекта базы данных, поэтому мы получаем обновления в реальном времени.

Стиль

Опять же, это всего лишь CSS, чтобы наша доска выглядела как доска, поэтому я не буду вставлять его сюда. Это должно быть в сущности выше.

Компонент приложения

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

Шаблон

В строке 4 мы визуализируем компонент Login, если поле состоянияuser не определено. Мы также перехватываем событие login, которое мы видели выше, и когда мы его перехватываем, мы вызываем функцию-обработчик login, которая определена в нашем блоке methods.

В строке 5 мы визуализируем компонент SelectGame, если пользователь определен. Мы также перехватываем create-game и join-game.

В строке 7 мы визуализируем компонент Gameboard. Мы делаем это, только если определено activeGame. Мы также передаем user и gameId реквизиты. Двоеточие является сокращением директивы v-bind, которая используется для привязки данных к компоненту. В этом случае мы привязываем наши поля gameId и user к компоненту, который будет реагировать при изменении этих значений.

Скрипт

Я возьму это понемногу, как раньше. Сначала мы импортируем базу данных из firebase. Мы также импортируем все наши компоненты, файл board.js (который я покажу через секунду; он просто содержит структуру данных, которую мы видели выше) и пакет uuid, который используется для генерации идентификаторов наших игр.

import { database } from "firebase";
import uuid from "uuid";
import board from "./board";
import Gameboard from "./components/Gameboard";
import Login from "./components/Login";
import SelectGame from "./components/SelectGame";

Затем мы экспортируем компонент и даем ему имя. Мы также определяем объект components, с помощью которого вы сообщаете Vue, что собираетесь использовать пользовательские компоненты в своем шаблоне. Все настраиваемые компоненты, которые вы собираетесь использовать, должны быть там.

export default {
  name: "app",
  components: { Gameboard, Login, SelectGame }

Мы добавляем поля состояния user, allUsers и activeGame, которые относятся к текущему авторизованному пользователю, всем пользователям в базе данных и идентификатору активной игры соответственно.

data: () => ({
  user: null,
  allUsers: [],
  activeGame: null,
}),

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

  • Добавить пользователя

Этот метод вызывается, когда событие login запускается из компонента Login, и он отвечает за получение правильного пользователя из allUsers (если этот пользователь существует) или создание нового.

В строке 30 он пытается найти пользователя из allUsers - списка, уже заполненного в методе жизненного цикла created, который мы увидим через минуту, - и, если он находит пользователя, он обновляет состояние (строка 33) и прерывается из функция.

Если он не находит пользователя, он создает пустой объект пользователя, а затем обновляет базу данных. Ссылка users указывает на всю коллекцию пользователей в базе данных, поэтому мы, по сути, берем ее, помещаем в нее нового пользователя, а затем перезаписываем его. Наконец, мы устанавливаем поле состояния user, равное нашему новому пользователю.

  • Создать игру

Эта функция вызывается, когда компонент SelectGame запускает событие create-game и просто добавляет игру в базу данных, а затем устанавливает поле activeGame. Свойство board, переданное в newGame, содержит структуру данных доски, которую мы видели ранее, так что она будет готова к использованию, когда мы начнем играть в игру. Файл хранится в src/board.js.

export default [
  {
    id: 'top-row',
    tiles: [
      { value: '', pos: '0,0' },
      { value: '', pos: '1,0' },
      { value: '', pos: '2,0' }
    ]
  },
  {
    id: 'middle-row',
    tiles: [
      { value: '', pos: '0,1' },
      { value: '', pos: '1,1' },
      { value: '', pos: '2,1' }
    ]
  },
  {
    id: 'bottom-row',
    tiles: [
      { value: '', pos: '0,2' },
      { value: '', pos: '1,2' },
      { value: '', pos: '2,2' }
    ]
  }
];
  • Присоединиться к игре
joinGame(id) {
  this.activeGame = id;
}

Эта функция вызывается из SelectGame, когда запускается событие join-game. Мы просим пользователя предоставить идентификатор самостоятельно (ожидается, что его друг отправил его им), поэтому нам не нужно делать здесь ничего особенного, кроме установки поля состояния activeGame. Затем появится компонент Gameboard, который заставит его использовать идентификатор для извлечения правильной игры из базы данных.

Последняя часть - это крючок жизненного цикла created. Это выглядит так.

Все это указывает firebase обновлять поле состояния allUsers всякий раз, когда изменяется коллекция users в нашей базе данных. Таким образом, компонент App всегда будет иметь доступ к самому актуальному списку пользователей.

Вот и все. Я сознательно выбрал довольно сложную задачу, потому что хотел показать, как Vue упростил задачу. Директивы v-if и v-for просты и лаконичны и делают код очень читаемым. Упрощенная структура компонентов также является огромным преимуществом и разделяет отдельные аспекты (шаблон, сценарий и стиль), но также достаточно близко, чтобы создавать логические и легкие связи в вашем мозгу, что отлично, если вы обладаете интеллектом бананового пюре, например сам. Я надеюсь, что это было полезно, и я надеюсь, что это вдохновит вас на более глубокое изучение Vue. Спасибо.