Это вторая и последняя часть серии по созданию приложения 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 с нуля.
Настройка React в Laravel
Laravel Mix был представлен в Laravel 5.4, и в настоящее время это идеальный способ соединить React и Laravel. С Laravel 5.5 весь процесс стал намного проще. Я описал оба метода ниже.
Использование команды React Preset (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 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.
ресурсы / активы / JS / компонент / Main.js
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.
ресурсы / активы / JS / app.js
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 , вы должны увидеть:
В package.json есть скрипт наблюдения, который автоматически компилирует ресурсы при обнаружении любых изменений. Чтобы включить этот режим, запустите npm run watch
.
Поздравляю, вы успешно настроили React для работы с Laravel. Теперь давайте создадим некоторые компоненты React для внешнего интерфейса.
Разработка приложения React
Если вы новичок в React, остальная часть учебника окажется несколько сложной. Я рекомендую пройти курс React Crash Course для начинающих, чтобы лучше познакомиться с концепциями React. Давайте начнем!
Приложение React построено вокруг компонентов. Компоненты являются наиболее важной структурой в React, и у нас есть каталог, предназначенный для компонентов.
Компоненты позволяют разделить пользовательский интерфейс на независимые, многократно используемые части и думать о каждой части по отдельности. Концептуально компоненты похожи на функции JavaScript. Они принимают произвольные входные данные (называемые «реквизитами») и возвращают элементы React, описывающие то, что должно появиться на экране.
— Официальные документы React
Для приложения, которое мы создаем, мы начнем с базового компонента, который отображает все продукты, возвращаемые сервером. Давайте назовем это Главным компонентом. Компонент должен позаботиться о следующих вещах изначально:
- Получить все продукты из API (GET / API / продукты).
- Храните данные о продукте в его состоянии.
- Показать данные о продукте.
React не является полноценным фреймворком, и, следовательно, библиотека сама по себе не имеет никаких функций AJAX. Я буду использовать fetch()
, который является стандартным JavaScript API для извлечения данных с сервера. Но существует множество альтернатив для выполнения AJAX-вызовов на сервер.
ресурсы / активы / JS / компонент / Main.js
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 и сохранить их в состоянии. Метод рендеринга используется для описания пользовательского интерфейса компонента. Все продукты отображаются в виде списка.
На странице просто перечислены названия продуктов, что скучно. Более того, у нас пока нет интерактивных элементов. Давайте сделаем название продукта кликабельным, и при клике будет отображена более подробная информация о продукте.
Отображение данных о продукте
Вот список вещей, которые мы должны охватить:
- Состояние для отслеживания продукта, на который была нажата кнопка. Давайте назовем это
currentProduct
с начальнымnull
значением. - При нажатии на название продукта
this.state.currentProduct
обновляется. - Детали продукта соответствующего продукта отображаются справа. Пока продукт не выбран, отображается сообщение «Продукт не выбран».
ресурсы / активы / JS / компонент / Main.js
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 принимает этот реквизит в качестве входных данных и предоставляет соответствующую информацию.
ресурсы / активы / JS / компонент / Main.js
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>
);
}
}
|
ресурсы / активы / JS / компонент / Product.js
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 ;
|
Приложение должно выглядеть примерно так:
Добавление нового продукта
Мы успешно реализовали интерфейс, соответствующий получению всех продуктов и их отображению. Далее нам нужна форма для добавления нового товара в список товаров. Процесс добавления продукта может показаться немного более сложным, чем просто выборка данных из API.
Вот что я считаю необходимым для разработки этой функции:
- Новый компонент с состоянием, который отображает пользовательский интерфейс для формы ввода. Состояние компонента содержит данные формы.
- При отправке дочерний компонент передает состояние основному компоненту с помощью обратного вызова.
- У компонента Main есть метод, скажем
handleNewProduct()
, который обрабатывает логику запуска запроса POST. После получения ответа главный компонент обновляет свое состояние (this.state.products
иthis.state.currentProduct
)
Звучит не очень сложно, не так ли? Давайте сделаем это шаг за шагом. Сначала создайте новый компонент. Я собираюсь назвать это AddProduct
.
ресурсы / активы / JS / компонент / AddProduct.js
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 раньше? о чем ты думаешь? Поделитесь ими с нами в комментариях.