Статьи

Создание приложения React с помощью Laravel Back-end: часть 2, React

Конечный продукт
Что вы будете создавать

Это вторая и последняя часть серии по созданию приложения React с бэкэндом Laravel. В первой части этой серии мы создали RESTful API с использованием Laravel для базового приложения для создания списка продуктов. В этом уроке мы будем разрабатывать интерфейс с использованием React.

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

В предыдущем уроке мы разработали приложение Laravel, которое отвечает на вызовы API. Мы создали маршруты, контроллер и модель для простого приложения с перечнем продуктов. Поскольку задачей контроллера было вернуть ответ на HTTP-запросы, раздел представления был полностью пропущен.

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

В этом уроке мы будем смещать акцент в сторону внешнего интерфейса. Первая часть руководства посвящена настройке React в среде Laravel. Я также познакомлю вас с Laravel Mix (поддерживаемым Laravel 5.4 и более поздними версиями), который является API для компиляции ресурсов. Во второй половине учебного курса мы начнем создавать приложение React с нуля.

Laravel Mix был представлен в Laravel 5.4, и в настоящее время это идеальный способ соединить React и Laravel. С Laravel 5.5 весь процесс стал намного проще. Я описал оба метода ниже.

Laravel 5.5 имеет совершенно новую функцию, которая позволяет создавать код для компонентов React с помощью preset react команды preset react artisan. В предыдущих версиях Laravel настроить React внутри Laravel было не так просто. Если вы работаете с последней версией Laravel, запустите приведенную ниже команду, чтобы добавить пресет React в ваш проект.

1
php artisan preset react

По умолчанию Laravel поставляется с предустановкой Vue, а указанная выше команда заменяет все экземпляры Vue на React. Интересно, что если вам не нужны предустановки, вы можете полностью удалить их, используя команду php artisan preset none .

Если все идет хорошо, это должно появиться в вашем терминале.

1
2
React scaffolding installed successfully.
Please run «npm install && npm run dev» to compile your fresh scaffolding.

На заднем плане Laravel использует Laravel Mix, который является гладкой оболочкой для веб-пакета. Webpack, как вы, возможно, уже знаете, представляет собой пакет модулей. Он разрешает все зависимости модуля и генерирует необходимые статические ресурсы для JavaScript и CSS. Для работы React необходим модуль-компоновщик, и веб-пакет отлично вписывается в эту роль. Таким образом, Laravel Mix — это слой, который находится поверх веб-пакета и облегчает использование веб-пакета в Laravel.

Лучшее понимание того, как работает Laravel Mix, важно, если вам потребуется настроить конфигурацию веб-пакета позже. Команда React preset не дает нам никакой информации о том, как все работает в фоновом режиме. Итак, давайте удалим пресет React и повторим шаги вручную.

Если вы работаете с Laravel 5.4 или вам просто интересно посмотреть, как настроен Laravel Mix, выполните следующие действия:

Установите « react , « react-dom и « babel-preset-react с использованием npm Также может быть хорошей идеей установить пряжу. Ни для кого не секрет, что Laravel и React предпочитают пряжу над нпм.

Зайдите на webpack.mix.js , расположенный в корневом каталоге вашего проекта Laravel. Это файл конфигурации, в котором вы объявляете, как должны компилироваться ваши ресурсы. Замените строку mix.js('resources/assets/js/app.js', 'public/js'); с mix.react('resources/assets/js/app.js', 'public/js'); , app.js является точкой входа для наших файлов JavaScript, и скомпилированные файлы будут находиться внутри public / js . Запустите npm install в терминале, чтобы установить все зависимости.

Затем перейдите к ресурсам / активам / JS . Там уже есть папка компонентов и пара других файлов JavaScript. Реактивные компоненты войдут в каталог компонентов. Удалите существующий файл Example.vue и создайте новый файл для примера компонента React.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from ‘react’;
import ReactDOM from ‘react-dom’;
 
/* An example React component */
class Main extends Component {
    render() {
        return (
            <div>
                <h3>All Products</h3>
            </div>
        );
    }
}
 
export default Main;
 
/* The if statement is required so as to Render the component on pages that have a div with an ID of «root»;
*/
 
if (document.getElementById(‘root’)) {
    ReactDOM.render(<Main />, document.getElementById(‘root’));
}

Обновите app.js, чтобы удалить весь код, связанный с Vue, и вместо этого импортируйте компонент React.

1
2
3
4
require(‘./bootstrap’);
 
/* Import the Main component */
import Main from ‘./components/Main’;

Теперь нам просто нужно сделать ресурсы доступными для просмотра. Файлы представлений находятся в каталоге resources / views . Давайте добавим <script> в welcome.blade.php , который является страницей по умолчанию, отображаемой при переходе на localhost:8000/ . Удалите содержимое файла представления и замените его кодом ниже:

ресурсы / виды / welcome.blade.php

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<!doctype html>
<html lang=»{{ app()->getLocale() }}»>
    <head>
        <meta charset=»utf-8″>
        <meta http-equiv=»X-UA-Compatible» content=»IE=edge»>
        <meta name=»viewport» content=»width=device-width, initial-scale=1″>
        <title>Laravel React application</title>
        <link href=»{{mix(‘css/app.css’)}}» rel=»stylesheet» type=»text/css»>
    </head>
    <body>
    <h2 style=»text-align: center»> Laravel and React application </h2>
        <div id=»root»></div>
        <script src=»{{mix(‘js/app.js’)}}» ></script>
    </body>
</html>

Наконец, выполните npm run dev или yarn run dev для компиляции ресурсов. Если вы посетите localhost: 8000 , вы должны увидеть:

Laravel и React бегут вместе
Реакция встроена в вид Laravel.

В package.json есть скрипт наблюдения, который автоматически компилирует ресурсы при обнаружении любых изменений. Чтобы включить этот режим, запустите npm run watch .

Поздравляю, вы успешно настроили React для работы с Laravel. Теперь давайте создадим некоторые компоненты React для внешнего интерфейса.

Если вы новичок в React, остальная часть учебника окажется несколько сложной. Я рекомендую пройти курс React Crash Course для начинающих, чтобы лучше познакомиться с концепциями React. Давайте начнем!

Приложение React построено вокруг компонентов. Компоненты являются наиболее важной структурой в React, и у нас есть каталог, предназначенный для компонентов.

Компоненты позволяют разделить пользовательский интерфейс на независимые, многократно используемые части и думать о каждой части по отдельности. Концептуально компоненты похожи на функции JavaScript. Они принимают произвольные входные данные (называемые «реквизитами») и возвращают элементы React, описывающие то, что должно появиться на экране.
Официальные документы React

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

  • Получить все продукты из API (GET / API / продукты).
  • Храните данные о продукте в его состоянии.
  • Показать данные о продукте.

React не является полноценным фреймворком, и, следовательно, библиотека сама по себе не имеет никаких функций AJAX. Я буду использовать fetch() , который является стандартным JavaScript API для извлечения данных с сервера. Но существует множество альтернатив для выполнения AJAX-вызовов на сервер.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React, { Component } from ‘react’;
import ReactDOM from ‘react-dom’;
 
/* Main Component */
class Main extends Component {
 
  constructor() {
   
    super();
    //Initialize the state in the constructor
    this.state = {
        products: [],
    }
  }
  /*componentDidMount() is a lifecycle method
   * that gets called after the component is rendered
   */
  componentDidMount() {
    /* fetch API in action */
    fetch(‘/api/products’)
        .then(response => {
            return response.json();
        })
        .then(products => {
            //Fetched product is stored in the state
            this.setState({ products });
        });
  }
 
 renderProducts() {
    return this.state.products.map(product => {
        return (
            /* When using list you need to specify a key
             * attribute that is unique for each list item
            */
            <li key={product.id} >
                { product.title }
            </li>
        );
    })
  }
   
  render() {
   /* Some css code has been removed for brevity */
    return (
        <div>
              <ul>
                { this.renderProducts() }
              </ul>
            </div>
       
    );
  }
}

Здесь мы инициализируем состояние products для пустого массива в конструкторе. Когда компонент монтируется, мы используем fetch() чтобы извлечь продукты из / api / products и сохранить их в состоянии. Метод рендеринга используется для описания пользовательского интерфейса компонента. Все продукты отображаются в виде списка.

Снимок экрана приложения React - Список всех продуктов

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

Вот список вещей, которые мы должны охватить:

  • Состояние для отслеживания продукта, на который была нажата кнопка. Давайте назовем это currentProduct с начальным null значением.
  • При нажатии на название продукта this.state.currentProduct обновляется.
  • Детали продукта соответствующего продукта отображаются справа. Пока продукт не выбран, отображается сообщение «Продукт не выбран».
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { Component } from ‘react’;
import ReactDOM from ‘react-dom’;
 
/* Main Component */
class Main extends Component {
 
  constructor() {
   
    super();
 
    /* currentProduct keeps track of the product currently
     * displayed */
    this.state = {
        products: [],
        currentProduct: null
    }
  }
  
  componentDidMount() {
    //code omitted for brevity
  }
 
 renderProducts() {
    return this.state.products.map(product => {
        return (
            //this.handleClick() method is invoked onClick.
            <li onClick={
                () =>this.handleClick(product)} key={product.id} >
                { product.title }
            </li>
        );
    })
  }
   
   handleClick(product) {
    //handleClick is used to set the state
    this.setState({currentProduct:product});
   
  }
   
  render() {
   /* Some css code has been removed for brevity */
    return (
        <div>
              <ul>
                { this.renderProducts() }
              </ul>
            </div>
       
    );
  }
}

Здесь мы добавили createProduct в состояние и инициализировали его значением null . Строка onClick={ () =>this.handleClick(product) } вызывает метод handleClick() при нажатии на элемент списка. Метод handleClick() обновляет состояние currentProduct .

Теперь для отображения данных о продукте мы можем либо отобразить их внутри основного компонента, либо создать новый компонент. Как упоминалось ранее, разделение пользовательского интерфейса на более мелкие компоненты — это способ действий React. Поэтому мы создадим новый компонент и назовем его Product.

Компонент Product находится внутри основного компонента. Главный компонент передает свое состояние в качестве реквизита. Компонент Product принимает этот реквизит в качестве входных данных и предоставляет соответствующую информацию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
render() {
  return (
    /* The extra divs are for the css styles */
        <div>
            <div>
             <h3> All products </h3>
              <ul>
                { this.renderProducts() }
              </ul>
            </div>
           
            <Product product={this.state.currentProduct} />
        </div>
    );
  }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { Component } from ‘react’;
 
/* Stateless component or pure component
 * { product } syntax is the object destructing
 */
const Product = ({product}) => {
    
  const divStyle = {
      /*code omitted for brevity */
  }
 
  //if the props product is null, return Product doesn’t exist
  if(!product) {
    return(<div style={divStyle}> Product Doesnt exist </div>);
  }
     
  //Else, display the product data
  return(
    <div style={divStyle}>
      <h2> {product.title} </h2>
      <p> {product.description} </p>
      <h3> Status {product.availability ?
      <h3> Price : {product.price} </h3>
      
    </div>
  )
}
 
export default Product ;

Приложение должно выглядеть примерно так:

Снимок экрана приложения React с отображением сведений о продукте

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

Вот что я считаю необходимым для разработки этой функции:

  • Новый компонент с состоянием, который отображает пользовательский интерфейс для формы ввода. Состояние компонента содержит данные формы.
  • При отправке дочерний компонент передает состояние основному компоненту с помощью обратного вызова.
  • У компонента Main есть метод, скажем handleNewProduct() , который обрабатывает логику запуска запроса POST. После получения ответа главный компонент обновляет свое состояние ( this.state.products и this.state.currentProduct )

Звучит не очень сложно, не так ли? Давайте сделаем это шаг за шагом. Сначала создайте новый компонент. Я собираюсь назвать это AddProduct .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class AddProduct extends Component {
 
  constructor(props) {
    super(props);
       /* Initialize the state.
       this.state = {
          newProduct: {
              title: »,
              description: »,
              price: 0,
              availability: 0
          }
        }
     
    //Boilerplate code for binding methods with `this`
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleInput = this.handleInput.bind(this);
  }
   
  /* This method dynamically accepts inputs and stores it in the state */
  handleInput(key, e) {
     
    /*Duplicating and updating the state */
    var state = Object.assign({}, this.state.newProduct);
    state[key] = e.target.value;
    this.setState({newProduct: state });
  }
 /* This method is invoked when submit button is pressed */
  handleSubmit(e) {
    //preventDefault prevents page reload
    e.preventDefault();
    /*A call back to the onAdd props.
     *state is passed as a param
     */
    this.props.onAdd(this.state.newProduct);
  }
 
  render() {
    const divStyle = {
        /*Code omitted for brevity */ }
     
    return(
      <div>
        <h2> Add new product </h2>
        <div style={divStyle}>
        /*when Submit button is pressed, the control is passed to
         *handleSubmit method
         */
        <form onSubmit={this.handleSubmit}>
          <label> Title:
           { /*On every keystroke, the handeInput method is invoked */ }
            <input type=»text» onChange={(e)=>this.handleInput(‘title’,e)} />
          </label>
           
          <label> Description:
            <input type=»text» onChange={(e)=>this.handleInput(‘description’,e)} />
          </label>
           
         { /* Input fields for Price and availability omitted for brevity */}
 
          <input type=»submit» value=»Submit» />
        </form>
      </div>
    </div>)
  }
}
 
export default AddProduct;

Компонент в основном отображает форму ввода, и все значения ввода хранятся в состоянии ( this.state.newProduct ). Затем при handleSubmit() формы handleSubmit() метод handleSubmit() . Но AddProduct должен передать информацию родителю, и мы делаем это с помощью обратного вызова.

Основной компонент, который является родительским, передает ссылку на функцию в качестве реквизита. Дочерний компонент, в нашем случае AddProduct , вызывает этот AddProduct , чтобы уведомить родителя об изменении состояния. Так что строка this.props.onAdd(this.state.newProduct); пример обратного вызова, который уведомляет родительский компонент о новом продукте.

Теперь внутри основного компонента мы объявим <AddProduct /> следующим образом:

1
<AddProduct onAdd={this.handleAddProduct} />

onAdd события handleAddProduct() методом handleAddProduct() компонента. Этот метод содержит код для отправки запроса POST на сервер. Если в ответе указано, что продукт был успешно создан, состояние products и currentProducts products обновляются.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
handleAddProduct(product) {
     
   product.price = Number(product.price);
   /*Fetch API for post request */
   fetch( ‘api/products/’, {
       method:’post’,
       /* headers are important*/
       headers: {
         ‘Accept’: ‘application/json’,
         ‘Content-Type’: ‘application/json’
       },
        
       body: JSON.stringify(product)
   })
   .then(response => {
       return response.json();
   })
   .then( data => {
       //update the state of products and currentProduct
       this.setState((prevState)=> ({
           products: prevState.products.concat(data),
           currentProduct : data
       }))
   })
 
 }

Не забудьте привязать метод handleProduct к классу, используя this.handleAddProduct = this.handleAddProduct.bind(this); в конструкторе. И вот окончательная версия приложения:

Скриншот финальной версии приложения

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
handleDelete() {
     
   const currentProduct = this.state.currentProduct;
   fetch( ‘api/products/’ + this.state.currentProduct.id,
       { method: ‘delete’ })
       .then(response => {
         /* Duplicate the array and filter out the item to be deleted */
         var array = this.state.products.filter(function(item) {
         return item !== currentProduct
       });
     
       this.setState({ products: array, currentProduct: null});
 
   });
 }
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
handleUpdate(product) {
 
   const currentProduct = this.state.currentProduct;
   fetch( ‘api/products/’ + currentProduct.id, {
       method:’put’,
       headers: {
         ‘Accept’: ‘application/json’,
         ‘Content-Type’: ‘application/json’
       },
       body: JSON.stringify(product)
   })
   .then(response => {
       return response.json();
   })
   .then( data => {
       /* Updating the state */
       var array = this.state.products.filter(function(item) {
         return item !== currentProduct
     })
       this.setState((prevState)=> ({
           products: array.concat(product),
           currentProduct : product
       }))
   })
 }

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

Мы прошли долгий путь от того, с чего начали. Сначала мы создали REST API с использованием фреймворка Laravel. Затем мы обсудили наши варианты смешивания Laravel и React. Наконец, мы создали интерфейс для API с использованием React.

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

За последние пару лет популярность React возросла. Фактически, у нас есть ряд товаров на рынке, которые доступны для покупки, просмотра, реализации и так далее. Если вы ищете дополнительные ресурсы по React, не стесняйтесь проверить их .

Вы пробовали экспериментировать с Laravel и React раньше? о чем ты думаешь? Поделитесь ими с нами в комментариях.