文章转发自专业的Laravel开发者社区,原始链接: https:// learnku.com/laravel/t/3 4304
我们将通过演示在 vue-router
进入一个路由之前,如何异步加载数据来继续使用 Laravel
构建我们的 Vue SPA
。
之前在 通过 Laravel 创建一个 Vue 单页应用(二) 中完成了 UsersIndex
组件异步地从 API
中加载用户。 简化了从数据库构建一个真实的后端 API
,选择通过 Laravel
的 factory()
方法在 API
返回中模拟假数据。
如果你还没有读过通过 Laravel
构建 Vue
单页应用的 第一部分 和 第二部分,我建议你先去看看,再回到这里。我会在这里等你。
这篇教程,我们将把模拟的 /users
返回替换为真正的由数据库支撑的。我习惯使用 MySQL
,但是你可以使用任何你想用的数据库驱动!
UsersIndex.vue
路由组件在生命周期 created()
中通过 API
加载数据。下面是第二部分结尾的 fetchData()
方法示例:
created() {
this.fetchData();
},
methods: {
fetchData() {
this.error = this.users = null;
this.loading = true;
axios
.get('/api/users')
.then(response => {
this.loading = false;
this.users = response.data;
}).catch(error => {
this.loading = false;
this.error = error.response.data.message || error.message;
});
}
}
我将演示如果通过组件的前置导航从 API
中提取数据,但是这之前我们需要让 API
输出一些真实数据。
创建一个真正的用户端点
我们将创建一个 UsersController
使用 Laravel 5.5 新的 API 资源 来返回 JSON 数据。
在创建控制器和 API 资源之前, 让我们首先设置一个数据库并且进行数据填充,以便为我们的 SPA 提供一些测试数据。
用户数据填充
我们使用 make:seeder
命令来创建一个用户填充:
php artisan make:seeder UsersTableSeeder
UsersTableSeeder
非常简单。我们使用模型工厂创建 50 个用户:
<?php
use IlluminateDatabaseSeeder;
class UsersTableSeeder extends Seeder
{
public function run()
{
factory(AppUser::class, 50)->create();
}
}
接下来,我们将 UsersTableSeeder
添加到 database/seeds/DatabaseSeeder.php
文件中:
<?php
use IlluminateDatabaseSeeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call([
UsersTableSeeder::class,
]);
}
}
如果不先创建和配置数据库,我们将不能使用数据填充。
配置数据库
是时候给我们的Vue SPA Laravel应用连接一个真实的数据库了。你可以通过使用类似TablePlus 的GUI工具来使用SQLite或者MySQL。如果你是Laravel的新手,你可以查阅在数据库入门上的大量文档。
如果你有一个运行在你设备上的MySQL实例,你可以使用以下命令行相当快速创建一个新数据库(假设你本地环境没有设置密码):
mysql -u root -e"create database vue_spa;"
# 或者通过-p参数来输入密码
mysql -u root -e"create database vue_spa;" -p
当你有了数据库,在 .env
文件添加配置DB_DATABASE=vue_spa
。如果你遇到了问题,请遵循文档,这样可以使您的数据库更容易地工作。
一旦你配置好了数据库连接,你可以迁移你的数据表和添加填充数据。Laravel附带了一个Users表的迁移,我们使用它来填充数据:
# 确保数据库seeders自动加载
composer dump-autoload
php artisan migrate:fresh --seed
如果你愿意,你也可以使用单独的artisan db:seed
命令!就像这样;你应该有一个包含50个用户的数据库,我们可以通过api查询和返回。
Users 控制器
第二章, 模拟的 /users
在 routes/api.php
中长下面这样:
Route::get('/users', function () {
return factory('AppUser', 10)->make();
});
我们来新建一个控制器类,这样可以在生产环境使用 php artisan route:cache
来获得一定的益处,这种方式不支持闭包。我们在命令行中同时创建控制器和 User API
资源类:
php artisan make:controller Api/UsersController
php artisan make:resource UserResource
第一命令是在 app/Http/Controllers/Api
目录中创建一个 User
控制器,第二个命令在 app/Http/Resources
目录中创建 UserResource
。
下面控制器和 Api
命名空间对应的的新 routes/api.php
代码:
Route::namespace('Api')->group(function () {
Route::get('/users', 'UsersController@index');
});
控制很直接;返回一个带分页的Eloquent API
:
<?php
namespace AppHttpControllersApi;
use AppUser;
use IlluminateHttpRequest;
use AppHttpControllersController;
use AppHttpResourcesUserResource;
class UsersController extends Controller
{
public function index()
{
return UserResource::collection(User::paginate(10));
}
}
下面是一个 JSON
响应的例子,和之前 UserResource
的 API
格式类似:
{
"data":[
{
"name":"Francis Marquardt",
"email":"schamberger.adrian@example.net"
},
{
"name":"Dr. Florine Beatty",
"email":"fcummerata@example.org"
},
...
],
"links":{
"first":"http://vue-router.test/api/users?page=1",
"last":"http://vue-router.test/api/users?page=5",
"prev":null,
"next":"http://vue-router.test/api/users?page=2"
},
"meta":{
"current_page":1,
"from":1,
"last_page":5,
"path":"http://vue-router.test/api/users",
"per_page":10,
"to":10,
"total":50
}
}
很奇妙,Laravel
自动加上了分页数据,并且将用户信息分配到 data
属性!
下面是 UserResource
类:
<?php
namespace AppHttpResources;
use IlluminateHttpResourcesJsonResource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* @param IlluminateHttpRequest $request
* @return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
}
UserResource
将集合中的每个 User
模型转换为数组,提供 UserResource::collection()
方法将用户的集合转换为 JSON
格式。
到现在,你应该有一个 /api/users
接口可以用在单页应用中,如果你继续学看下去,你会注意到新的返回已经不满足当前的组件。
修改 UsersIndex 组件
我们可以通过调整 then()
来调用用户数据所在的 data
键,来很快的让 UsersIndex.vue
组件重新工作。刚开始的时候它看起来有点新颖,但是 response.data
是一个响应对象,因此我们可以这样设置用户数据:
this.users = response.data.data;
fetchData()
和新 API 配合调整后的方法如下:
fetchData() {
this.error = this.users = null;
this.loading = true;
axios
.get('/api/users')
.then(response => {
this.loading = false;
this.users = response.data.data;
}).catch(error => {
this.loading = false;
this.error = error.response.data.message || error.message;
});
}
导航前读取数据
我们的组件通过我们新的API来运作,现在是演示如何在导航到组件之前获取用户信息的绝佳时机。 通过使用这种方法,我们可以在获取数据之后导航到新路线。我们可以通过使用beforeRouteEnter
守卫在进入组件之前实现。例子Vue路由文档如下:
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
查阅文档有完整的示例,但只需说我们将异步获取用户数据,并且只有在完成之后我们才会触发 next()
和在组件里设置数据(变量 vm
)
检查文档以获得完整的示例,但只需说我们将异步获取用户数据,一旦完成,并且只有在完成之后,我们才会触发next(,并在组件上设置数据(变量vm
)。
以下是getUsers
函数可能看起来像是异步从API获取用户,然后触发对组件的回调:
const getUsers = (page, callback) => {
const params = { page };
axios
.get('/api/users', { params })
.then(response => {
callback(null, response.data);
}).catch(error => {
callback(error, error.response.data);
});
};
注意,该方法不返回Promise,而是在完成或失败时触发回调。回调传递两个参数:一个错误和来自API调用的响应。
我们的getUsers()
方法接受一个 page
变量,该变量最终作为查询字符串参数出现在请求中。如果为空(路由中没有传递页码),则API将默认设为page=1
。
最后我要指出的是const params
值。它实际上是这样的:
{
params: {
page: 1
}
}
下面是我们的beforeRouteEnter
守卫如何使用getUsers
函数获取异步数据,然后在组件上调用next()
设置它:
beforeRouteEnter (to, from, next) {
const params = {
page: to.query.page
};
getUsers(to.query.page, (err, data) => {
next(vm => vm.setData(err, data));
});
},
这是从API返回数据后的 getUsers()
调用中的callback
参数:
(err, data) => {
next(vm => vm.setData(err, data));
}
然后在API成功响应时,在getUsers()
中这样调用:
callback(null, response.data);
beforeRouteUpdate
当组件已经处于渲染状态,并且路由更改时,将调用beforeRouteUpdate
,并且Vue会在新路由中复用组件。例如,当我们的用户从/users?page=2
跳转到 /users?page=3
。
beforeRouteUpdate
调用类似于beforeRouteEnter
。但是,前者可以在组件中使用 this
,因此在样式上会略有不同:
// 当路由更改并且组件已经渲染时,
// 逻辑会略有不同。
beforeRouteUpdate (to, from, next) {
this.users = this.links = this.meta = null
getUsers(to.query.page, (err, data) => {
this.setData(err, data);
next();
});
},
由于组件处于渲染状态,我们需要在从API获取下一组用户之前重置一些数据属性。我们可以访问组件。因此,我们可以先调用this.setData()
(我还没有向您展示),然后不需要回调就调用next()
。
最后,这是在UsersIndex
组件中的setData
方法:
setData(err, { data: users, links, meta }) {
if (err) {
this.error = err.toString();
} else {
this.users = users;
this.links = links;
this.meta = meta;
}
},
setData()
方法通过使用对象析构来获取。 data
, links
和 meta
键来自于API的响应。我们清晰地使用data: users
将 data
赋值给新变量users
。
一起捆绑UsersIndex
我已经向您展示了该UsersIndex组件的各个部分,我们已经准备好将所有组件捆绑在一起,并进行一些非常基本的分页。本教程未向您展示如何构建分页,因此您可以自己找到(或创建)奇特的分页!
分页是一种很好的方式,向您展示如何以vue-router编程方式浏览SPA 。
这是带有我们新的挂钩和方法的完整组件,这些新挂钩和方法可使用路由器挂钩获取异步数据:
<template>
<div class="users">
<div v-if="error" class="error">
<p>{{ error }}</p>
</div>
<ul v-if="users">
<li v-for="{ id, name, email } in users">
<strong>Name:</strong> {{ name }},
<strong>Email:</strong> {{ email }}
</li>
</ul>
<div class="pagination">
<button :disabled="! prevPage" @click.prevent="goToPrev">Previous</button>
{{ paginatonCount }}
<button :disabled="! nextPage" @click.prevent="goToNext">Next</button>
</div>
</div>
</template>
<script>
import axios from 'axios';
const getUsers = (page, callback) => {
const params = { page };
axios
.get('/api/users', { params })
.then(response => {
callback(null, response.data);
}).catch(error => {
callback(error, error.response.data);
});
};
export default {
data() {
return {
users: null,
meta: null,
links: {
first: null,
last: null,
next: null,
prev: null,
},
error: null,
};
},
computed: {
nextPage() {
if (! this.meta || this.meta.current_page === this.meta.last_page) {
return;
}
return this.meta.current_page + 1;
},
prevPage() {
if (! this.meta || this.meta.current_page === 1) {
return;
}
return this.meta.current_page - 1;
},
paginatonCount() {
if (! this.meta) {
return;
}
const { current_page, last_page } = this.meta;
return `${current_page} of ${last_page}`;
},
},
beforeRouteEnter (to, from, next) {
getUsers(to.query.page, (err, data) => {
next(vm => vm.setData(err, data));
});
},
// when route changes and this component is already rendered,
// the logic will be slightly different.
beforeRouteUpdate (to, from, next) {
this.users = this.links = this.meta = null
getUsers(to.query.page, (err, data) => {
this.setData(err, data);
next();
});
},
methods: {
goToNext() {
this.$router.push({
query: {
page: this.nextPage,
},
});
},
goToPrev() {
this.$router.push({
name: 'users.index',
query: {
page: this.prevPage,
}
});
},
setData(err, { data: users, links, meta }) {
if (err) {
this.error = err.toString();
} else {
this.users = users;
this.links = links;
this.meta = meta;
}
},
}
}
</script>
如果更容易理解,这里是作为GitHub Gist的UsersIndex.vue。
这里有很多新事物,因此我将指出一些更重要的观点。该goToNext()
和goToPrev()
方法演示了如何使用导航vue-router
使用this.$router.push
:
this.$router.push({
query: {
page: `${this.nextPage}`,
},
});
我们正在将新页面推送到触发的查询字符串beforeRouteUpdate
。我还要指出的是,我向您展示<button>
了上一个和下一个动作的元素,主要是为了演示通过编程方式进行导航的过程vue-router
,您很可能会使用它<router-link />
来自动在分页路线之间导航。
我引入了三个计算属性(nextPage
,prevPage
和paginatonCount
)来确定下一页和上一页的页码,并paginatonCount
显示了当前页码的可视计数和总页数。
下一个和上一个按钮使用计算出的属性来确定是否应禁用它们,而goTo
方法使用这些计算出的属性将page
查询字符串参数推入下一页或上一页。当下一页或上一页在第一页和最后一页的边界处为空时,将禁用这些按钮。
代码中可能有一些冗余,但是此组件说明vue-router
了在进入路由之前用于获取数据的方法!
不要忘记确保通过运行Laravel Mix构建最新版本的JavaScript:
# NPM
npm run dev
# Watch to update automatically while developing
npm run watch
# Yarn
yarn dev
# Watch to update automatically while developing
yarn watch
最后,这是我们更新完整的UsersIndex.vue
组件后显示出的SPA结果:
下一步是什么
我们现在有一个有效的API,可以从数据库中获取真实数据,还有一个简单的分页组件,该组件在后端使用Laravel的API模型资源进行简单的分页链接并将数据包装在数据
键中。
接下来,我们将致力于创建,编辑和删除用户。 一个/ users
资源将被锁定在一个实际的应用程序中,但是目前,我们只是在构建CRUD功能来学习如何与vue-router
一起使用来异步导航和提取数据。
我们还可以将axios客户端代码从组件中抽象出来,但是现在,这很简单,因此我们将其保留在组件中,直到第4部分。一旦添加了其他API功能,我们将想要创建专用的 HTTP客户端的模块。
您可以继续进行第4部分-编辑现有用户