Предпосылки

Прочитать главу 1 | О чем все это?
Прочтите главу 2 | Что такое React и почему он крутой?
Прочтите главу 3 | Строим наши дома
Прочитать главу 4 | Наша первая деревня
Прочитать главу 5 | Дополнения к нашим домам
Прочитать главу 6 | Строим наш первый проект
Прочитать главу 7 | Создание нашего первого пользовательского интерфейса
Прочтите главу 8 | Реализация React Router

Получите официальную электронную книгу

Если вы хотите поддержать меня, вы можете перейти сюда, чтобы получить официальную копию этой электронной книги в форматах PDF, MOBI и EPUB.

Сборник кода

Через главу 5: http://bit.ly/2qRNkQX
Глава 6+: https://github.com/michaelmang/React-for-the-Visual-Learner

Определение «Демо чат-бота»

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

Причина в том, что я не был уверен в том, насколько сложным я хотел это сделать.

С одной стороны, это могло быть очень просто. Вместо того, чтобы на самом деле создавать чат-бота, мы могли бы просто сделать несколько визуальных трюков, чтобы он выглядел так, как будто в нашем демонстрационном представлении есть демонстрация чат-бота. Например, у нас может быть изображение канала Slack без сообщения. Через несколько секунд мы меняем изображение на канал Slack с сообщением пользователя чат-боту. Еще через несколько секунд мы могли переключить изображение на канал Slack с ответом от чат-бота. Затем мы могли бы повторить этот процесс. В целом, это создаст визуальный вид демонстрации чат-бота.

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

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

Состояние нашего приложения

Под общей картиной я подразумеваю, где наше текущее приложение React вписывается в архитектуру клиент-сервер веб-приложений.

Ранее мы обсуждали, что приложения React используют данные и инструктируют DOM о том, что следует отображать. Все это происходит на стороне клиента.

На стороне сервера есть сервер, на котором размещается наше приложение и который обслуживает его браузер. Сервер может извлекать данные из базы данных и отправлять их в наше приложение React, где мы можем использовать их в компонентах.

В нашем приложении мы написали только клиентский код. Мы создали наше приложение React и использовали Webpack, чтобы взять наше приложение, применить Babel для предварительной обработки и выплюнуть конечный продукт. В package.json мы настроили webpack-dev-server (который мы установили через npm) для запуска по следующей команде:

npm start

webpack-dev-server размещает наше последнее приложение и передает его браузеру. Затем мы можем ввести следующий адрес в нашем браузере, чтобы увидеть наше приложение:

http://localhost:8080/

React Router контролирует, какое представление является активным, и соответственно обновляет URL-адрес.

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

Функциональность нашей демонстрации чат-бота

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

Итак, что мы собираемся делать с нашей демонстрацией чат-бота?

Я собираюсь приземлиться между полностью работающим чат-ботом и просто изменением изображений. Давайте вместе рассмотрим возможности нашего приложения.

Во-первых, мы собираемся добавить внутренние компоненты для сообщения пользователя (UserMessage) и сообщения чат-бота (ChatBotMessage). Оба эти компонента будут вложены в наш компонент SlackChannel. Следовательно, компонент SlackChannel будет увеличен с домашнего компонента (поскольку в настоящее время это просто изображение) к компоненту блока (поскольку он будет вкладывать наши новые компоненты).

Макет компонента SlackChannel с новыми компонентами UserMessage и ChatBotMessage можно увидеть ниже:

Поскольку компонент SlackChannel используется в деревне LandingPage и деревне Demo, мы будем использовать условную визуализацию, чтобы компоненты UserMessage и ChatBotMessage отображались только для деревни Demo.

Хотя мы хотим, чтобы демонстрационная деревня в конечном итоге отображала блок SlackChannel с домами UserMessage и ChatBotMessage, как показано на макете выше, мы хотим раскрыть их в определенном порядке.

Мы будем определять время раскрытия с помощью хуков жизненного цикла и контролировать то, что отрисовывается, используя более условный рендеринг. Когда компонент SlackChannel монтируется, у нас будет только пустой образ Slack Channel:

Через 1 секунду после этого мы отрендерим компонент ChatBotMessage:

Через 1 секунду мы отрендерим последний компонент UserMessage:

Хотя мы не будем создавать собственный сервер, мы будем использовать облачную базу данных под названием Firebase. Firebase будет хранить информацию, используемую для заполнения компонентов UserMessage и ChatBotMessage. В частности, имена пользователей, цвета аватаров, тексты сообщений и ссылка на статью будут сохраняться и извлекаться из Firebase для заполнения компонентов вместо того, чтобы мы вручную передавали реквизиты.

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

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

Понимание Firebase

Firebase - это серверная часть как услуга. Короче говоря, он берет на себя большую часть внутренней разработки (авторизация, базы данных и т. Д.). В нашем случае он может предоставить облачную базу данных для хранения и синхронизации данных без необходимости писать собственный код серверной части. Он будет выполнять большую часть традиционной настройки и кодирования базы данных за кулисами. Это позволит нам почувствовать использование данных из базы данных в приложении React, не перегружая себя.

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

Приступим к изучению Firebase. Чтобы изучить Firebase, мы создадим мини-приложение Tile Background Changer через Codepen.

Настройка мини-приложения Tile Background Changer

Прежде всего, приступим и разветвите мой шаблон, который содержит все HTML, CSS и соответствующие настройки React.

Затем переименуйте его в Tile Background Changer | Демонстрация Firebase с использованием React.

Функциональность мини-приложения Tile Background Changer

Давайте рассмотрим, что мы хотим от этого мини-приложения.

Мы собираемся использовать один компонент под названием Tile, вложенный 3 раза в компонент приложения. Каждый раз, когда компонент Tile вложен, мы передаем класс как опору. Это позволяет нам повторно использовать 1 компонент 3 раза, но с разными классами для каждого из них для стилизации.

Цвет фона каждой плитки будет контролироваться с помощью встроенного стиля JSX. Мы инициализируем состояние и будем иметь свойство под названием color (которое будет меняться, поэтому мы используем состояние), которое будет иметь начальное значение шестнадцатеричного кода для белого (#FFFFFF).

JSX компонента Tile будет отображать три элемента. Одним из элементов будет плитка, цвет которой определяется состоянием. Еще одним элементом будет текстовое поле, которое будет содержать шестнадцатеричный код текущего цвета. Последний элемент будет фактическим текстом шестнадцатеричного кода.

Вот изображение начальных плиток (перед любыми событиями):

При щелчке по каждой плитке у нас будет обработчик событий, который будет читать определенный шестнадцатеричный код, который мы будем хранить в базе данных Firebase. Значение шестнадцатеричного кода из базы данных будет установлено как новый цвет в состоянии. Это приведет к изменению цвета фона плитки и текста шестнадцатеричного кода под плиткой следующим образом:

Вот последнее приложение

Настройка компонентов React

Во-первых, нам нужен компонент для нашего приложения, который будет вкладывать компоненты плитки:

//App
class App extends React.Component {
  render() {
    return (
      <div className="container">
      </div>
    )
  }
}

Затем давайте добавим часть ReactDOM:

//"inject" to DOM
ReactDOM.render(
  <App/>,
  document.getElementById('app')
);

Кусок пирога!

Теперь давайте определим оболочку нашего компонента Tile:

class Tile extends React.Component {
render() {
    return (
      <div className={this.props.class}>
        <div className="color-text-box">
          <div className="color-text">
          </div>
        </div>
      </div>
    )
  }
}

Как упоминалось ранее, мы хотим, чтобы этот компонент отображал плитку (плитка-1, плитка-2 или плитка-3, в зависимости от свойства класса, которое мы передадим), поле, содержащее текст текущего цвета (цвет-текст -box) и фактический текст текущего цвета (color-text). Элементы вложены друг в друга для стилизации.

Затем давайте вложим этот компонент в наш компонент App и определим свойство с именем initialColor с шестнадцатеричным кодом для белого цвета:

//App
class App extends React.Component {
  render() {
    return (
      <div className="container">
        <Tile class={"tile-1"}/>
      </div>
    )
  }
}

У нас осталось еще 3 шага для завершения нашего первого компонента плитки:

  • Инициализируйте состояние с помощью свойства под названием color и присвойте ему значение #FFFFFF
  • Используйте встроенный стиль JSX, чтобы для фона элемента tile-1 было установлено свойство цвета, определенное на предыдущем шаге.
  • Вставить текст свойства цвета в элемент color-text

Для первого шага мы можем добавить следующее:

class Tile extends React.Component {
  
state = {color: "#FFFFFF"};
render() {
    return (
      <div className="{this.props.class}">
        <div className="color-text-box">
          <div className="color-text">
          </div>
        </div>
      </div>
    )
  }
}

Поскольку свойству color присвоено значение свойства initialColor, мы сделали color: this.props.initialColor.

Чтобы завершить следующий шаг, мы добавляем следующий код для встроенного стиля JSX:

class Tile extends React.Component {
  
  state = {color: "#FFFFFF"};
render() {
    return (
      <div style={{background: this.state.color}} className={this.props.class}>
        <div className="color-text-box">
          <div className="color-text">
          </div>
        </div>
      </div>
    )
  }
}

И последнее, но не менее важное: мы можем ввести свойство цвета следующим образом:

class Tile extends React.Component {
  
  state = {color: "#FFFFFF"};
render() {
    return (
      <div style={{background: this.state.color}} className="{this.props.class}">
        <div className="color-text-box">
          <div className="color-text">
            {this.state.color}
          </div>
        </div>
      </div>
    )
  }
}

Теперь мы должны увидеть следующее:

Чтобы добавить еще две плитки, мы повторно используем наш компонент Tile с другим свойством класса:

//App
class App extends React.Component {
  render() {
    return (
      <div className="container">
        <Tile class={"tile-1"}/>
        <Tile class={"tile-2"}/>
        <Tile class={"tile-3"}/>
      </div>
    )
  }
}

Потрясающие! Прежде чем мы начнем добавлять обработчики событий, нам нужно настроить нашу базу данных в Firebase.

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

Перейдите в Firebase и выберите Начать.

Вы попадете на следующую панель управления:

Нажмите Добавить проект и назовите его React Test, как видите, я уже сделал.

Вы попадете на страницу, где много всего происходит:

Не поддавайтесь желанию уйти и исследовать и выберите слева База данных.

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

Структура данных в Firebase

Если вы знакомы с базами данных SQL, вы можете ожидать, что наши данные будут структурированы в виде таблиц и строк. Однако Firebase хранит свои данные в виде объектов JSON. Затем данные в Firebase структурируются как дерево JSON.

Легко объяснить, что это package.json в нашем проекте response-landing-page:

{
  "name": "react-landing-page",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^15.5.4",
    "react-dom": "^15.5.4",
    "react-grid-system": "^2.9.1",
    "react-router-dom": "^4.1.1"
  },
  "devDependencies": {
    "babel-core": "^6.24.1",
    "babel-loader": "^7.0.0",
    "babel-preset-env": "^1.5.1",
    "babel-preset-react": "^6.24.1",
    "html-webpack-plugin": "^2.28.0",
    "node-sass": "^4.5.3",
    "sass-loader": "^6.0.5",
    "webpack": "^2.6.0",
    "webpack-dev-server": "^2.4.5"
  }
}

Здесь у нас есть данные о нашем проекте, структурированные в формате name и value. Один из примеров:

"name": "react-landing-page"
//name: value

Иногда данные должны быть вложены в узел, например, в сценариях, зависимостях и devDependencies в нашем package.json. Один из примеров:

"scripts": {
  "start": "webpack-dev-server"
}
//node
  //name: value

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

Вот еще один пример дерева JSON для представления некоторых данных, которые могут понадобиться приложению:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "role": "Developer",
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

Не так уж плохо, а?

Единственная другая важная информация, которую следует предоставить, - это то, что у нас может быть 32 уровня вложенных узлов. Однако вы хотите, чтобы структурированные данные были как можно более плоскими (с наименьшей вложенностью). Причина в том, что если вы запрашиваете доступ к одному узлу, вы также получаете доступ ко всем дочерним элементам. Следовательно, доступ к узлу с большим количеством дочерних узлов может снизить производительность.

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

Создание нашей базы данных

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

Переходим в Firebase, и мы должны быть в представлении базы данных:

Здесь мы можем создать наше дерево JSON с помощью этого приятного пользовательского интерфейса.

Наведите указатель мыши на реакцию-тест… и нажмите +.

Сначала нам нужно добавить узел под названием цвета. Просто введите это в имя, а затем нажмите +, чтобы мы увидели:

Теперь мы можем добавить пары имя / значение для нашего узла цветов. Имя будет именем цвета, а значение будет шестнадцатеричным кодом.

Мы будем использовать следующие имя и пары. Обратите внимание, что вы должны вводить их как есть, а кавычки добавлять не следует:

mint: #B4EBCA
pink: #FFB7C3
purple: #4C2C69

После их ввода у нас должно быть следующее:

Прохладный!

Настройка нашей базы данных

Это звучит как болезненный шаг, однако все очень просто.

Нажмите Обзор в меню слева, чтобы открыть следующее представление:

Нажмите Добавить firebase в свое веб-приложение.

Появится следующее всплывающее окно:

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

Сначала скопируйте только URL-адрес между первым тегом скрипта:

https://www.gstatic.com/firebasejs/4.1.0/firebase.js

Добавьте это как внешний ресурс в свою ручку, например:

Сохрани и закрой.

Затем добавьте следующий код в верхнюю часть столбца JS:

Я сделал небольшое изменение, заменив var на const, поскольку это хорошая практика - сделать эту переменную неизменной (мы не хотим ничего изменять в этой переменной).

Теперь мы успешно настроили нашу ручку с нашим проектом Firebase.

Чтение данных

По умолчанию чтение и запись данных заблокированы, если пользователь не аутентифицирован. Нас не беспокоит аутентификация, так что давайте это изменим.

Нажмите База данных в меню слева, а затем выберите вкладку Правила. Давайте сделаем чтение и запись доступными без аутентификации:

Разобравшись с этим, теперь нам нужно установить ссылку на нашу базу данных следующим образом:

//config here
const database = firebase.database();
//App component here

В частности, мы хотим сослаться на узел colors из нашей базы данных. Поэтому давайте добавим еще одну переменную:

//config here
const database = firebase.database();
const colors = database.ref('colors');
//App component here

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

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

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

//App
class App extends React.Component {
  componentDidMount () {  
    
  }
  
  render() {
    return (
      <div className="container">
        <Tile class={"tile-1"}/>
        <Tile class={"tile-2"}/>
        <Tile class={"tile-3"}/>
      </div>
    )
  }
}

Затем мы добавим код, который говорит: «Я собираюсь опробовать эту ссылку базы данных на узел цветов. Если там есть значение, я хочу сделать снимок узла и отобразить его на консоли ».

componentDidMount () {  
  colors.on('value', function(snapshot) {
    console.log(snapshot.val())
  }); 
}

Если вы войдете в инструменты разработчика, а затем на вкладку консоли, мы должны увидеть, что объект данных для узла цветов регистрируется:

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

Прохладный! Теперь давайте попробуем распечатать шестнадцатеричный код для каждого цвета в этом объекте следующим образом:

componentDidMount () {  
    colors.on('value', function(snapshot) {
      console.log(snapshot.val().mint)
      console.log(snapshot.val().pink)
      console.log(snapshot.val().purple)
    }); 
  }

Теперь мы должны увидеть в консоли следующее:

Идеально! Нам не нужен этот крючок жизненного цикла, поэтому вы можете удалить его.

Добавление обработчика событий

Теперь, когда мы понимаем, как получить доступ к нашим данным Firebase, мы сделаем следующее на каждой плитке:

  • Добавьте обработчик событий, который будет вызываться при щелчке элемента плитки
  • Пусть обработчик событий получит случайный цвет (любой из 3 шестнадцатеричных кодов в нашей базе данных)
  • Затем обработчик событий обновит свойство цвета в состоянии компонента плитки случайным цветом.

Сначала мы добавляем оболочку обработчика событий:

class Tile extends React.Component {
  
state = {color: this.props.initialColor};
updateTile() {
    
}
render() {
    return (
      <div style={{background: this.state.color}} className="{this.props.class}">
        <div className="color-text-box">
          <div className="color-text">
            {this.state.color}
          </div>
        </div>
      </div>
    )
  }
}

Затем давайте вызовем это при щелчке по элементу div с className tile-1:

class Tile extends React.Component {
  
  state = {color: this.props.initialColor};
  updateTile() {
    
  }
render() {
    return (
      <div onClick={this.updateTile.bind(this)} style={{background: this.state.color}} className="tile-1">
        <div className="color-text-box">
          <div className="color-text">
            {this.state.color}
          </div>
        </div>
      </div>
    )
  }
}

После этого давайте добавим код, который заставит обработчик событий выбирать случайный цвет из нашей базы данных:

updateTile() {
    //get random number between 1 and 3
    const random = Math.floor((Math.random() * 3) + 1);
    
    //empty variable for selectedColor
    let selectedColor;
    
    //retrieve color depending on random number
    colors.on('value', function(snapshot) {
      if(random === 1) {
        selectedColor = snapshot.val().mint;
      } else if (random === 2) {
        selectedColor = snapshot.val().pink;
      } else {
        selectedColor = snapshot.val().purple;
      }
    }); 
  }

В приведенном выше коде мы получаем случайное число от 1 до 3. Если оно равно 1, мы выбираем монетный двор из Firebase. Если 2, выбираем розовый. Наконец, если это 3, то выбираем фиолетовый.

Последней частью является использование setState для обновления свойства цвета, которое управляет фоном плитки, чтобы оно было значением переменной selectedColor:

updateTile() {
    //get random number between 1 and 3
    const random = Math.floor((Math.random() * 3) + 1);
    
    //empty variable for selectedColor
    let selectedColor;
    
    //retrieve color depending on random number
    colors.on('value', function(snapshot) {
      if(random === 1) {
        selectedColor = snapshot.val().mint;
      } else if (random === 2) {
        selectedColor = snapshot.val().pink;
      } else {
        selectedColor = snapshot.val().purple;
      }
    }); 
    
    //setState of color property to selected color
    this.setState({color: selectedColor});
  }

Теперь мы можем щелкнуть первую плитку и увидеть следующее:

Так круто! Однако вы заметили, что при первом щелчке плитка была пустой.

Что случилось?

Что ж, следующий код должен дождаться, пока Firebase не вернет запрошенное значение:

//retrieve color depending on random number
    colors.on('value', function(snapshot) {
      if(random === 1) {
        selectedColor = snapshot.val().mint;
      } else if (random === 2) {
        selectedColor = snapshot.val().pink;
      } else {
        selectedColor = snapshot.val().purple;
      }
    }); 

Следовательно, selectedColor, скорее всего, не будет иметь значения к моменту появления этой строки:

this.setState({color: selectedColor});

Из-за этого новому установленному состоянию назначается пустой цвет.

Как это исправить?

Мы можем использовать обещание. Давайте обновим наш код, и я объясню:

let promise = new Promise((resolve, reject) => {
      colors.on('value', function(snapshot) {
        if(random === 1) {
          selectedColor = snapshot.val().mint;
        } else if (random === 2) {
          selectedColor = snapshot.val().pink;
        } else {
          selectedColor = snapshot.val().purple;
        }
        resolve(selectedColor);
      }); 
});
promise.then((resolvedColor) => {
      this.setState({color: resolvedColor});
});

Теперь мы, по сути, говорим: «Хорошо, this.setState, я знаю, что вы хотите обновить цвет, используя значение, которое мы получаем из Firebase. Однако на это потребуется время. Я обещаю, что как только мы сможем разрешить это , тогда вы сможете продолжить и обновить цвет, используя полученное значение ».

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

resolve (selectedColor) передает новое (разрешенное) значение selectedColor функции внутри prom.then (…). this.setState затем использует этот параметр для нового значения свойства цвета. Поскольку this.setState ждет, пока не будет получено значение Firebase, мы больше не будем обновлять свойство цвета до пустого значения.

Для более подробного объяснения обещания, ознакомьтесь с документацией MDN.

Заключительное перо: http://bit.ly/2skjKkm

Краткое описание возможностей демонстрации нашего чат-бота

Теперь, когда мы получили поддержку Firebase, мы вернемся к демонстрации нашего чат-бота.

Подведем итоги. Вот что нам нужно сделать для демонстрации нашего чат-бота.

Нам нужно переместить компонент SlackChannel с домашнего компонента на блочный. В него будут вложены 2 компонента, UserMessage и ChatBotMessage, которые в конечном итоге будут отображаться как:

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

Давай приступим к работе!

Создание демонстрационной базы данных чат-бота

Во-первых, нам нужно создать новый проект Firebase. Перейдите к Обзор в Firebase, нажмите стрелку вниз рядом с названием текущего проекта и выберите Создать новый проект:

Назовите его Демо чат-бота и выберите Создать проект:

Когда это будет завершено, нажмите База данных слева, а затем перейдите на вкладку Правила. Еще раз, мы хотим разрешить чтение и запись без аутентификации:

По завершении обновления нажмите Опубликовать.

Перейдите на вкладку Данные и попробуйте самостоятельно составить следующее дерево данных:

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

Установка Firebase через npm

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

Кроме того, cd в корень проекта в командной строке.

Когда вы будете готовы, давайте установим firebase в качестве зависимости:

npm install --save firebase

Холодные бобы.

Настройка наших компонентов сообщений

Давайте создадим 2 новых файла компонентов, которые будут находиться в папке «Дома»: User Message.js и Chat Bot Message.js.

Прежде чем мы добавим к ним некоторый код, перетащите файл Slack Channel.js в папку Blocks, поскольку теперь он будет блочным компонентом.

Для начала добавим базовую настройку в User Message.js:

import React from 'react';
import ReactDOM from 'react-dom';
class UserMessage extends React.Component {
  constructor(){
    super()
    this.state = {avatar: "", username: "", message: ""}
  }
}
render() {
    return (
      <div className="user-message">
        <div style={{background: this.state.avatar}} className="user-avatar"></div>
        <div className="username">{this.state.username}</div>
        <div className="message">{this.state.message}</div>
      </div>
    )
  }
}
module.exports = UserMessage;

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

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

Важное замечание: я немного изменил способ инициализации состояния. Я сделал это в целях лучшей практики.

constructor(){
    super()
    this.state = {avatar: "", username: "", message: ""}
  }

Метод конструктора будет обрабатывать инициализацию нашего класса компонентов. Поскольку наш класс компонента является дочерним классом React.component:

class UserMessage extends React.Component

нам нужно вызвать конструктор этого родительского класса, используя:

constructor(){
    super()
    //...
}

Теперь мы можем инициализировать состояние, чтобы к нему можно было получить доступ, выполнив this.state .___, поэтому мы делаем:

constructor(){
    super()
    this.state = {avatar: "", username: "", message: ""}
  }

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

Перейдем к Chat Bot Message.js:

import React from 'react';
import ReactDOM from 'react-dom';
class ChatBotMessage extends React.Component {
constructor(){
    super()
    this.state = {avatar: "", username: "", message: "", article: ""}
  }
}
render() {
    return (
      <div className="bot-message">
        <div style={{background: this.state.avatar}} className="bot-avatar"></div>
        <div className="username">{this.state.username}</div>
        <div className="message">{this.state.message}</div>
        <a href={this.state.article}>
          <span className="article">{this.state.article}</span>
        </a>
      </div>
    )
  }
}
module.exports = ChatBotMessage;

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

Мы хотим, чтобы это в конечном итоге отображалось как:

Опять же, есть что добавить, но этого достаточно для начальной настройки.

Настройка Firebase

Теперь, когда мы создали базу данных, установили Firebase через npm и настроили наши домашние компоненты, нам теперь нужно получить код конфигурации.

Опять же, мы переходим к представлению Обзор в консоли Firebase.

Затем выберите Добавить Firebase в свое веб-приложение и скопируйте код конфигурации, который будет примерно таким:

var config = {
    apiKey: "AIzaSyA1JBmMSIg7X_lSEHr3vh7sEc_p83hB4g4",
    authDomain: "chat-bot-demo.firebaseapp.com",
    databaseURL: "https://chat-bot-demo.firebaseio.com",
    projectId: "chat-bot-demo",
    storageBucket: "chat-bot-demo.appspot.com",
    messagingSenderId: "740047634033"
  };

В этом файле давайте вставим приведенную выше форму кода в верхнюю часть User Message.js, изменим переменную на const и изменим имя переменной на FirebaseConfig:

import React from 'react';
import ReactDOM from 'react-dom';
const config = {
    apiKey: "AIzaSyA1JBmMSIg7X_lSEHr3vh7sEc_p83hB4g4",
    authDomain: "chat-bot-demo.firebaseapp.com",
    databaseURL: "https://chat-bot-demo.firebaseio.com",
    projectId: "chat-bot-demo",
    storageBucket: "chat-bot-demo.appspot.com",
    messagingSenderId: "740047634033"
  };
firebase.initializeApp(config);
//UserMessage component stuff

Мы также должны добавить импорт для firebase, которая была установлена ​​через npm:

import React from 'react';
import ReactDOM from 'react-dom';
import * as firebase from "firebase";
const config = {
    apiKey: "AIzaSyA1JBmMSIg7X_lSEHr3vh7sEc_p83hB4g4",
    authDomain: "chat-bot-demo.firebaseapp.com",
    databaseURL: "https://chat-bot-demo.firebaseio.com",
    projectId: "chat-bot-demo",
    storageBucket: "chat-bot-demo.appspot.com",
    messagingSenderId: "740047634033"
  };
firebase.initializeApp(config);

Примечание. Нам нужно, чтобы код конфигурации появлялся в нашем приложении только один раз. Поэтому мы просто сохраним его в User Message.js, но есть несколько способов сделать это.

Получение данных из Firebase

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

Давайте продолжим в User Message.js.

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

//imports and Firebase config here
const database = firebase.database();
const user = database.ref('user');
//component here

Затем давайте добавим жизненный цикл, в котором мы будем получать данные Firebase:

class UserMessage extends React.Component {
  constructor(){
    super()
    this.state = {avatar: "", username: "", message: ""}
  }
  componentDidMount() {
    let firebaseAvatar;
    let firebaseUsername;
    let firebaseMessage;
  }
  //more stuff
}

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

В ручке Tile Background Changer нам нужно было использовать обещание при получении данных Firebase:

//fetch data
let promise = new Promise((resolve, reject) => {
      colors.on('value', function(snapshot) {
        if(random === 1) {
          selectedColor = snapshot.val().mint;
        } else if (random === 2) {
          selectedColor = snapshot.val().pink;
        } else {
          selectedColor = snapshot.val().purple;
        }
        resolve(selectedColor);
      }); 
});
//then do something with it (update the state)
promise.then((resolvedColor) => {
  this.setState({color: resolvedColor});
});

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

Поэтому давайте добавим в наш файл следующее обещание:

class UserMessage extends React.Component {
  constructor(){
    super()
    this.state = {avatar: "", username: "", message: ""}
  }
  componentDidMount() {
    let firebaseAvatar;
    let firebaseUsername;
    let firebaseMessage;
    let promise1 = new Promise((resolve, reject) => {
      user.on('value', function(snapshot) {
        firebaseAvatar = snapshot.val().avatar;
        resolve(firebaseAvatar);
      });
    });
  }
 
  //more stuff
}

В этом обещании мы собираемся получить снимок базы данных Firebase по указанной нами ссылке и получить значение аватара. Значение будет сохранено в firebaseAvatar.

В отличие от нашего пера, у нас есть несколько значений для выборки. Следовательно, нам нужно отдельное обещание для каждого значения, которое необходимо получить (поэтому оно называется обещанием1).

Мы добавим другие обещания до того, как приступим к работе с обещанием.then, поскольку есть способ сделать это для всех наших обещаний сразу.

Вот оставшиеся обещания:

//promise 1 here
let promise2 = new Promise((resolve, reject) => {
      user.on('value', function(snapshot) {
        firebaseUsername = snapshot.val().username;
        resolve(firebaseUsername);
      });
    });
let promise3 = new Promise((resolve, reject) => {
      user.on('value', function(snapshot) {
        firebaseMessage = snapshot.val().message;
        resolve(firebaseMessage);
      });
    });

Прохладный. Затем мы хотим взять все разрешенные данные и обновить наше состояние. Для этого мы можем сделать следующее:

Promise.all([promise1, promise2, promise3]).then(values => {
  this.setState({
    avatar: values[0], 
    username: values[1], 
    message: values[2]
  })
});

В приведенном выше коде мы говорим: «Возьмите все наши обещания и сохраните разрешенные значения в массиве. Используйте эти значения в массиве для обновления наших свойств состояния ».

Чтобы увидеть, как это работает, введите значения в консоль:

Promise.all([promise1, promise2, promise3]).then(values => {
  this.setState({
    avatar: values[0], 
    username: values[1], 
    message: values[2]
  })
  console.log(values);
});

Теперь мы увидим массив значений и поймем, как мы использовали их для обновления нашего состояния:

Потрясающие!

Перейдем к Chat Bot Message.js.

Мы начнем с добавления импорта firebase и ссылок на базы данных:

import * as firebase from "firebase";
const database = firebase.database();
const bot = database.ref('bot');

Затем мы можем добавить код жизненного цикла и обещаний, как и в предыдущем файле, за исключением того, что у нас есть дополнительные данные для получения статьи:

componentDidMount() {
    let firebaseAvatar;
    let firebaseUsername;
    let firebaseMessage;
    let firebaseArticle;
let promise1 = new Promise((resolve, reject) => {
      bot.on('value', function(snapshot) {
        firebaseAvatar = snapshot.val().avatar;
        resolve(firebaseAvatar);
      });
    });
let promise2 = new Promise((resolve, reject) => {
      bot.on('value', function(snapshot) {
        firebaseUsername = snapshot.val().username;
        resolve(firebaseUsername);
      });
    });
let promise3 = new Promise((resolve, reject) => {
      bot.on('value', function(snapshot) {
        firebaseMessage = snapshot.val().message;
        resolve(firebaseMessage);
      });
    });
let promise4 = new Promise((resolve, reject) => {
      bot.on('value', function(snapshot) {
        firebaseArticle = snapshot.val().article;
        resolve(firebaseArticle);
      });
    });
Promise.all([promise1, promise2, promise3, promise4]).then(values => {
      this.setState({avatar: values[0], username: values[1], message: values[2], article: values[3]})
    });
  }

Все различия между предыдущим файлом выделены жирным шрифтом.

Мы также можем зарегистрировать разрешенные значения, чтобы убедиться, что они работают:

Promise.all([promise1, promise2, promise3, promise4]).then(values => {
      this.setState({
        avatar: values[0], 
        username: values[1], 
        message: values[2], 
        article: values[3]
      })
      console.log(values);
});

Идеально!

Обновление нашего компонента SlackChannel

Чтобы протестировать наш код, сначала нам нужно обновить файл Slack Channel.js.

Нам нужно вложить эти новые компоненты сообщения следующим образом:

import React from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col } from 'react-grid-system';
import UserMessage from './houses/User Message.js';
import ChatBotMessage from './houses/Chat Bot Message.js';
class SlackChannel extends React.Component {
  render() {
    return (
      <Col lg={12}>
        <img className="slack-channel" src="./images/Slack Channel.svg"/>
        <UserMessage/>
        <ChatBotMessage/>
      </Col>
    )
  }
}
module.exports = SlackChannel;

Затем давайте обновим наш CSS кодом, который вы можете найти здесь.

Если вы еще этого не сделали, cd войдите в каталог проекта и запустите npm start.

Затем перейдите на локальный хост.

Теперь мы должны увидеть следующее:

Превосходно!

Следует отметить пару моментов.

Одна проблема заключается в том, что мы хотим, чтобы эти сообщения отображались только в демонстрационном представлении, но они также отображаются и в представлении LandingPage. Кроме того, мы упомянули, что хотим, чтобы эти сообщения раскрывались по порядку.

По всем этим причинам пришло время добавить условный рендеринг.

Добавление условного рендеринга

Slack Channel.js в настоящее время содержит:

import React from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col } from 'react-grid-system';
import UserMessage from './houses/User Message.js';
import ChatBotMessage from './houses/Chat Bot Message.js';
class SlackChannel extends React.Component {
  render() {
    return (
      <Col lg={12}>
        <img className="slack-channel" src="./images/Slack Channel.svg"/>
        <UserMessage/>
        <ChatBotMessage/>
      </Col>
    )
  }
}
module.exports = SlackChannel;

Мы хотим использовать условный рендеринг, чтобы управлять отображением UserMessage и ChatBotMessage. Мы хотим, чтобы они отображались только в демонстрационной деревне, однако и деревня LandingPage, и деревня Demo используют заголовок, содержащий SlackChannel.

Чтобы контролировать это, мы передадим опору (называемую messages) в компонент Header, вложенный как в Landing Page.js, так и в Demo.js :

Landing Page.js

import React from 'react';
import ReactDOM from 'react-dom';
import Header from './neighborhoods/Header.js';
import SectionA from './neighborhoods/Section A.js';
import SectionB from './neighborhoods/Section B.js';
import SectionC from './neighborhoods/Section C.js';
import Footer from './neighborhoods/Footer.js';
class LandingPage extends React.Component {
  render() {
    return (
      <div className="landing-page">
        <Header messages={false} title={"Developer Bot for Slack"} subtitle={"One article to one random person in your Slack group. Once a day."}/>
        <SectionA/>
        <SectionB/>
        <SectionC title={"Developer Bot for Slack"} subtitle={"One article to one random person in your Slack group. Once a day."}/>
        <Footer/>
      </div>
    )
  }
}
module.exports = LandingPage;

Demo.js

import React from 'react';
import ReactDOM from 'react-dom';
import Header from './neighborhoods/Header.js';
import Footer from './neighborhoods/Footer.js';
class Demo extends React.Component {
  render() {
    return (
      <div className="demo">
        <Header messages={true} title={"Chat Bot Demo"} subtitle={"Watch how it works."}/>
        <Footer/>
      </div>
    )
  }
}
module.exports = Demo;

Если свойство messages истинно, компоненты сообщения будут отображаться. Вот почему мы передаем значение false в Landing Page.js и значение true в Demo.js.

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

Следующий уровень ниже компонентов LandingPage и Demo - это наш заголовок.

В Header.js давайте передадим поддержку сообщений компоненту SlackChannel следующим образом:

import React from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col } from 'react-grid-system';
import Navigation from './blocks/Navigation.js';
import CallToAction from './blocks/Call To Action.js';
import SlackChannel from './blocks/Slack Channel.js';
class Header extends React.Component {
  render() {
    return (
      <section className="header">
        <Container>
          <Row>
            <Navigation/>
          </Row>
          <Row>
            <CallToAction title={this.props.title} subtitle={this.props.subtitle}/>
          </Row>
          <Row>
            <SlackChannel messages={this.props.messages}/>
          </Row>
        </Container>
      </section>
    )
  }
}
module.exports = Header;

Теперь мы можем применить условный рендеринг в Slack Channel.js следующим образом:

import React from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col } from 'react-grid-system';
import UserMessage from './houses/User Message.js';
import ChatBotMessage from './houses/Chat Bot Message.js';
class SlackChannel extends React.Component {
  render() {
    //Boolean flag passed down for deciding what to render
    const messages = this.props.messages;
    return (
      <Col lg={12}>
        <img className="slack-channel" src="./images/Slack Channel.svg"/>
        {messages ? (
          //render messages is true (demo)
          <div>
            <UserMessage/>
            <ChatBotMessage/>
          </div>
        ) : (
          //render nothing messages is false (landing page)
          <div></div>
        )}
      </Col>
    )
  }
}
module.exports = SlackChannel;

Выделенный код, который мы рассмотрели ранее, можно прочитать как: «Если свойство унаследованных сообщений истинно, то компонент Demo будет отображать эти компоненты сообщения. В противном случае мы визуализируем пустой тег div, чтобы отображалось только изображение канала Slack ».

Если мы обновим наш локальный хост, мы увидим, что представление Landing Page не отображает эти компоненты сообщения.

Мы можем, наконец, завершить эту демонстрацию чат-бота, раскрыв компоненты сообщения по порядку:

Давайте начнем добавлять код в Chat Bot Message.js, чтобы все заработало.

Сначала мы добавим в наше локальное состояние еще одно свойство под названием showComponent:

constructor(){
    super()
    this.state = {avatar: "", username: "", message: "", article: "", showComponent: false}
  }

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

  1. Он будет изменен с false на true.
  2. Нет компонента более высокого уровня, который будет определять, как мы применяем условный рендеринг.

Второй шаг - обновить нашу функцию рендеринга, чтобы мы могли отображать сообщение в зависимости от значения (true или false) showComponent:

render() {
    const showComponent = this.state.showComponent;
    return (
      <div>
        {showComponent ? (
          //render component
          <div className="bot-message">
            <div style={{background: this.state.avatar}} className="bot-avatar"></div>
            <div className="username">{this.state.username}</div>
            <div className="message">{this.state.message}</div>
            <a href={this.state.article}>
              <span className="article">{this.state.article}</span>
            </a>
          </div>
        ) : (
          //render nothing
          <div></div>
        )}
      </div>
    )
  }

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

У нас будет состояние обновления в хуке жизненного цикла componentDidMount через 1,5 секунды . Мы будем использовать setTimeout для управления временем:

componentDidMount() {
    let firebaseAvatar;
    let firebaseUsername;
    let firebaseMessage;
    let firebaseArticle;
    //promise stuff here
    setTimeout(() => {
      this.setState({showComponent: true});
    }, 1500)
}

Через 1,5 секунды (1500) мы изменим showComponent на true. Это приведет к отображению сообщения бота.

Файл User Message.js будет иметь такие же точные обновления с одним незначительным отличием. Будет указана продолжительность setTimeout, равная 3000. Это означает, что после монтирования компонента будет ждать 3 секунды:

setTimeout(() => {
  this.setState({showComponent: true});
}, 3000)

Это приведет к его отображению после ChatBotMessage.

Перезагрузите локальный хост, и теперь вы должны увидеть, что у нас есть работающий чат-бот.

Наконец-то! Мы закончили все основное кодирование для этой книги!

Похлопайте себя по спине. Ты сделал это!

Поскольку это было долгое время, я посвящу целую главу заключительным мыслям. На этом закончим.

Окончательный код

Последний код главы 9 доступен на Github.

Глава 10

Глава 10 уже доступна.

Получите официальную электронную книгу

Если вы хотите поддержать меня, вы можете перейти сюда, чтобы получить официальную копию этой электронной книги в форматах PDF, MOBI и EPUB.

Пожалуйста, предоставьте любезные исправления и отзывы. Разбейте сердце и поделитесь.

С уважением,
Майк Манджаларди
Основатель Coding Artist