Если вы хотите, чтобы ваше одностраничное приложение Vue.js связывалось с бэкэндом Laravel , вы вполне разумно подумаете об использовании AJAX. Действительно, Laravel поставляется с библиотекой Axios, загруженной по умолчанию.
Однако не рекомендуется использовать AJAX для получения состояния приложения при начальной загрузке страницы, поскольку для этого требуется дополнительная передача данных на сервер, что задержит рендеринг приложения Vue.
Я вижу много приложений с полным стеком Vue / Laravel, созданных таким образом. Альтернативой этому анти-шаблону является введение начального состояния приложения в заголовок HTML-страницы, чтобы оно было доступно приложению, как только оно понадобится. Затем AJAX можно использовать более подходящим образом для последующих выборок данных.
Однако использование такого подхода может привести к путанице, если у вашего приложения разные маршруты, требующие другого начального состояния. В этой статье я продемонстрирую шаблон проектирования, который упрощает реализацию этого подхода и обеспечивает большую гибкость даже в приложениях с несколькими маршрутами.
Как вы вскоре увидите, созданный мной пример приложения интерактивнее на 25% быстрее при реализации этого шаблона проектирования.
Вам также может понравиться:
Восемь самых больших ошибок разработки Laravel, которых вы можете легко избежать .
Передача данных в Vue из Laravel
Вот пример полнофункционального приложения Vue / Laravel, которое я создал для Oldtime Cars, вымышленного продавца старинных автомобилей. Приложение имеет титульную страницу, которая показывает доступные автомобили, и общую страницу с подробностями, которая показывает специфику конкретной модели.
Это приложение использует Vue Router для обработки навигации по страницам. Каждая страница нуждается в данных из бэкэнда (например, название модели автомобиля, цена и т. Д.), Поэтому требуется механизм для передачи их между Vue и Laravel. Стандартный шаблон проектирования — это настройка конечных точек API для каждой страницы в Laravel, а затем использование beforeRouteEnter
ловушки Vue Router для асинхронной загрузки данных через AJAX перед переходом страницы.
Проблема такой архитектуры в том, что она дает нам неоптимальный процесс загрузки для начальной загрузки страницы:
Исключение запроса AJAX здесь сделает страницу интерактивной гораздо быстрее, особенно при медленном интернет-соединении.
Ввод начального состояния приложения
Если мы введем начальное состояние приложения в HTML-страницу, Vue Router не потребуется запрашивать его с сервера, поскольку он уже будет доступен на клиенте.
Мы можем реализовать это путем JSON-кодирования состояния на стороне сервера и присвоения его глобальной переменной:
index.html
HTML
1
<html>
2
...
3
<head>
4
...
5
<script type="text/javascript">
6
window.__INITIAL_STATE__ = '{ "cars": [ { "id": 1 "name": "Buick", ... }, { ... } ] }'
7
</script>
8
</head>
9
<body>
10
<div id="app"></div>
11
</body>
12
</html>
Затем приложение тривиально получает доступ и использует состояние:
JavaScript
xxxxxxxxxx
1
let initialState = JSON.parse(window.__INITIAL_STATE__);
2
new Vue({
4
5
})
Этот подход устраняет необходимость в запросе AJAX и сокращает начальный процесс загрузки приложения до следующего:
Я предоставил отчеты Lighthouse внизу статьи, чтобы показать улучшение времени загрузки.
Примечание: этот подход не подходит, если исходное состояние приложения включает конфиденциальные данные. В этом случае, возможно, вы могли бы использовать «гибридный» подход, при котором на страницу вставляются только нечувствительные данные, а конфиденциальные данные извлекаются посредством аутентифицированного вызова API.
Реализация в многопутевом приложении
Этот подход достаточно хорош, как есть, в приложении только с одним маршрутом, или, если вы готовы ввести начальное состояние каждой страницы на каждой запрашиваемой странице. Но у Oldtime Cars есть несколько маршрутов, и было бы гораздо эффективнее внедрить только начальное состояние текущей страницы.
Это означает, что у нас есть следующие проблемы:
- Как мы можем определить, какое начальное состояние нужно вставить в запрос страницы, так как мы не знаем, на какую страницу пользователь изначально попадет?
- Когда пользователь переходит на другой маршрут из приложения, как приложение узнает, нужно ли ему загружать новое состояние или просто использовать введенное состояние?
Типы навигации
Vue Router способен фиксировать любые изменения маршрута, которые происходят внутри страницы, и обрабатывать их без обновления страницы. Это означает, что нажали ссылки или команды JavaScript, которые меняют местоположение браузера.
Но изменения маршрута из браузера, например, адресной строки или ссылок на приложение с внешних страниц, не могут быть перехвачены Vue Router и приведут к загрузке новой страницы.
Основная концепция шаблона проектирования
Имея это в виду, мы должны убедиться, что каждая страница имеет необходимую логику для получения своих данных либо путем инъекции в страницу, либо через AJAX, в зависимости от того, загружается ли страница заново с сервера или с помощью Vue Router.
Реализовать это проще, чем кажется, и лучше всего понять через демонстрацию, поэтому давайте пройдемся по коду Oldtime Cars и я покажу вам, как я это сделал.
Вы можете увидеть полный код в этом репозитории GitHub .
Настройка бэкэнда
Маршруты
Поскольку сайт имеет две страницы, существует два различных маршрута: домашний маршрут и подробный маршрут. Шаблон проектирования требует, чтобы маршруты обслуживались либо представлениями, либо в виде полезных нагрузок JSON, поэтому я создал маршруты для веб-страниц и API для каждого из них:
маршруты / web.php
PHP
xxxxxxxxxx
1
2
Route::get('/', 'CarController@home_web');
4
Route::get('/detail/{id}', 'CarController@detail_web');
маршруты / api.php
PHP
xxxxxxxxxx
1
2
Route::get('/', 'CarController@home_api');
4
Route::get('/detail/{id}', 'CarController@detail_api');
контроллер
Я сократил часть кода для экономии места, но основная идея заключается в следующем: веб-маршруты возвращают представление с начальным состоянием приложения, введенным в заголовок страницы (шаблон показан ниже), в то время как маршруты API возвращают точно такое же состояние, только в качестве полезной нагрузки.
(Также обратите внимание, что в дополнение к состоянию данные включают в себя path
. Мне понадобится это значение во внешнем интерфейсе, как вы вскоре увидите).
приложение / HTTP / Контроллеры / CarController.php
PHP
xxxxxxxxxx
1
2
namespace App\Http\Controllers;
4
use Illuminate\Http\Request;
6
class CarController extends Controller
8
{
9
/* This function returns the data for each car, by id */
11
public function get_cars($id) { ... }
12
/* Returns a view */
14
public function detail_web($id)
15
{
16
$state = array_merge([ 'path' => '/detail/' . $id], $this->get_cars($id));
17
return view('app', ['state' => $state]);
18
}
19
/* Returns a JSON payload */
21
public function detail_api($id)
22
{
23
$state = array_merge([ 'path' => '/detail/' . $id], $this->get_cars($id));
24
return response()->json($state);
25
}
26
public function home_web() { ... }
28
public function home_api() { ... }
30
}
Посмотреть
Я использую один и тот же шаблон для каждой страницы. Его единственная известная особенность заключается в том, что он будет кодировать состояние как JSON в заголовке:
ресурсов / просмотров / app.blade.php
HTML
xxxxxxxxxx
1
2
<html>
3
<head>
4
<script type="text/javascript">
5
window.__INITIAL_STATE__ = "{!! addslashes(json_encode($fields)) !!}";
6
</script>
7
</head>
8
<body>
9
<div id="app"...>
10
</body>
11
</html>
Настройка внешнего интерфейса
маршрутизатор
Интерфейс приложения использует стандартную настройку Vue Router. У меня есть разные компоненты для каждой страницы, то есть Home.vue и Detail.vue .
Обратите внимание, что маршрутизатор находится в режиме истории, потому что я хочу, чтобы каждый маршрут обрабатывался отдельно.
ресурсы / активы / JS / app.js
JavaScript
xxxxxxxxxx
1
import Vue from 'vue';
2
import VueRouter from 'vue-router';
3
Vue.use(VueRouter);
5
import Home from './components/Home.vue';
7
import Detail from './components/Detail.vue';
8
const router = new VueRouter({
10
mode: 'history',
11
routes: [
12
{ path: '/', component: Home },
13
{ path: '/detail/:id', component: Detail }
14
]
15
});
16
const app = new Vue({
18
el: '#app',
19
router
20
});
Компоненты страницы
Там очень мало происходит в компонентах страницы. Ключевая логика в миксине, который я покажу позже.
Home.vue
HTML
xxxxxxxxxx
1
<template>
2
<div>
3
<h1>Oldtime Cars</h1>
4
<div v-for="car in cars"...>
5
</div>
6
</template>
7
<script>
8
import mixin from '../mixin';
9
export default {
11
mixins: [ mixin ],
12
data() {
13
return {
14
cars: null
15
}
16
}
17
};
18
</script>
Mixin
Этот миксин необходимо добавить ко всем компонентам страницы, в данном случае Home и Detail . Вот как это работает:
- Добавляет
beforeRouteEnter
хук к каждому компоненту страницы. Когда приложение загружается впервые или когда меняется маршрут, вызывается этот хук. Это, в свою очередь, вызываетgetData
метод. - В
getData
грузах метода нагнетаемого состояния и проверяютpath
свойство. Исходя из этого, он определяет, может ли он использовать введенные данные или ему нужно получать новые данные. Если последнее, он запрашивает соответствующую конечную точку API с HTTP-клиентом Axios. - Когда обещание, возвращаемое из
getData
разрешения, разрешается,beforeRouteEnter
ловушка будет использовать все возвращаемые данные и назначит ихdata
свойству этого компонента.
mixin.js
JavaScript
xxxxxxxxxx
1
import axios from 'axios';
2
let getData = function(to) {
4
return new Promise((resolve, reject) => {
5
let initialState = JSON.parse(window.__INITIAL_STATE__) || {};
6
if (!initialState.path || to.path !== initialState.path) {
7
axios.get(`/api${to.path}`).then(({ data }) => {
8
resolve(data);
9
})
10
} else {
11
resolve(initialState);
12
}
13
});
14
};
15
export default {
17
beforeRouteEnter (to, from, next) {
18
getData(to).then((data) => {
19
next(vm => Object.assign(vm.$data, data))
20
});
21
}
22
};
Благодаря реализации этого миксина компоненты страницы имеют необходимую логику для получения своего начального состояния либо из данных, введенных на страницу, либо через AJAX, в зависимости от того, была ли страница загружена с сервера или была перемещена в / из Vue Router.
Улучшения производительности для старых автомобилей
Я создал несколько отчетов о производительности приложения, используя расширение Lighthouse Chrome.
Если я пропущу все вышеперечисленное и вернусь к стандартному шаблону загрузки начального состояния приложения из API, отчет Lighthouse будет следующим:
Одной из метрик релевантности является время первой значимой краски , которое здесь составляет 2570 мс.
Давайте сравним это с улучшенной архитектурой:
При загрузке начального состояния приложения изнутри страницы, а не из API, время до первой значимой рисования сократилось до 2050 мс, что на 25% больше.