使用 Laravel 和 Vuejs 构建一个 SPA 单页应用
我们将继续使用Laravel构建我们的Vue SPA 单页应用,向您展示如何在vue-router进入路由之前异步加载数据。
我们在 使用Laravel构建Vue SPA单页应用教程第二节 完成了一个UsersIndex Vue组件,该组件异步请求API加载用户数据。我们不需要构建一个真正的由后端返回数据的API,可以使用Laravel factory()方法返回模拟数据。
如果您还没有阅读过用Laravel构建 Vue SPA的第1部分 和 第2部分,我建议你先从这些帖子开始,然后再回来。我会等你的!
在本教程中,我们将真正的从数据库中请求数据来替换模拟的用户数据。我喜欢使用MySQL,但是你可以使用任何你擅长的数据库!
我们的UsersIndex.vue
组件使用了“Created()”钩子方法从API加载数据。在第2部分的结尾处,“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
返回 JSON 数据,JSON数据将使用 Laravel 5.5 中新功能 API resources 生成资源。
在创建控制器和 API 资源之前,我们要先设置数据库并且填充一些测试数据给 SPA 单页应用
用户数据填充
我们可以使用make:seeder
命令创建一个新的用户数据填充:
php artisan make:seeder UsersTableSeeder
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
public function run()
{
factory(App\User::class, 50)->create();
}
}
下一步, 让我们添加 UsersTableSeeder
到 我们的 database/seeds/DatabaseSeeder.php
文件:
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call([
UsersTableSeeder::class,
]);
}
}
我们在执行数据填充前必须要创建和配置好数据库
配置数据库
是时候让我们的 Vue SPA Laravel 应用连接一个真正的数据库了. 你可以使用带GUI的SQLite例如TablePlus或者使用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 自带了用户表的迁移文件和填充数据文件:
# 为了确保数据库填充器能够自动加载
composer dump-autoload
php artisan migrate:fresh --seed
你也可以单独使用 artisan db:seed
命令如果你喜欢的话! 然后, 你数据库应该就有了50位用户,可以通过API进行查询和返回.
用户控制器
如果你回看第二节,在 routes/api.php
中的 /users
假数据接口就像这样的:
Route::get('/users', function () {
return factory('App\User', 10)->make();
});
让我们来创建一个控制器类,这也给了我们在生产环境中使用 php artisan route:cache
的附加好处,这对闭包来说是不可能的,我们会使用以下命令来创建控制器和用户的资源 API
php artisan make:controller Api/UsersController
php artisan make:resource UserResource
第一条命令是在 app/Http/Controllers/Api
下的 Api
文件夹下添加一个 User 控制器,第二条命令是在 app/Http/Resources
文件夹下添加一个UserResource 。
routes/api.php
新代码包含我们的控制器和 Api
命名空间:
Route::namespace('Api')->group(function () {
Route::get('/users', 'UsersController@index');
});
控制器非常简单;我们返回一个带分页的 Eloquent API 资源:
<?php
namespace App\Http\Controllers\Api;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
class UsersController extends Controller
{
public function index()
{
return UserResource::collection(User::paginate(10));
}
}
当我们连接 UserResource
API格式 ,返回JSON 的例子看起来是这样的:
{
"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为我们提供了分页数据并自动将 users 添加到 data
键!
这是 UserResource
类:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
}
UserResource
类将每个在集合中的 User
模型转换为数组,并且提供了 UserResource::collection()
方法将用户集合转换为 JSON 格式。
现在,你应该有了一个在 SPA 应用中可以使用的 /api/users
接口了,如果你正跟着步骤操作,你可能会注意到我们新的响应格式中断了组件。
修复 UsersIndex 组件
我们想要 UsersIndex.vue
组件重新正常工作的话,要调整一下 then()
方法去获取新的响应格式里 data
中的数据。可能看起来有点古怪。response.data
是响应对象,所以用户数据要用如下代码获取。
this.users = response.data.data;
下面是根据新 API 接口调整后的 fetchData()
方法:
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
守卫在传入的成分。一个来自 the vue-router documentation 的 看起来像这样例子:
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
查看完整的示例文件之后,可以肯定地说,我们将异步获取用户数据,一旦完成之后,在这之后,我们触发 next()
和在组件中的数据集(the vm
variable)。
这是一个可能看起来像异步获取用户API的 GetUsers
方法,然后触发一个回调函数的组件:
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);
});
};
注意,该方法不会返回一个响应,而是在完成或失败时触发回调。回调传递参数、错误和API调用的响应。
我们的 getusers()
方法接受 一个page
变量作为最终查询字符串参数的要求。如果它是空的(没有任何页面通过此路由),然后这个接口会自动申明 page= 1
。
最后我要指出的是 常量参数
的值,它将像这样生效:
{
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));
});
},
这块是 回调
参数在 getuses()
调用之后从API返回数据:
(err, data) => {
next(vm => vm.setData(err, data));
}
在这里就需要像getusers()
这里一样API成功的响应:
callback(null, response.data);
beforeRouteUpdate钩子
当组件已经是渲染状态,路由改变时,将调用 beforeRouteUpdate
钩子,并且在新路由中重用这个组件。例如,当我们用户导航从 /users?page=2
到 /users?page=3
。
beforeRouteUpdate
的调用类似于 beforeRouteEnter
。 然而,前者在组件中可以访问 this
, 因此,逻辑略有不同:
// 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();
});
},
因为组件是渲染状态,从 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()
方法对新 API 响应获取的 data
对象解构出users
、links
和 meta
。为了清楚,我们给 data:users
的数据分配新的变量名 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>
如果想更容易消化,这里是 UsersIndex.vue as a GitHub Gist.
这里有很多新东西,所以我将指出一些更重要的观点。 goToNext()
和 goToPrev()
方法说明你如何通过使用 this.$router.push
导航到
vue-router`:
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 模型资源,可以返回简单分页链接并将返回内容包裹在 data
键里。
下一步,我们要创建、编辑、删除用户。在生产环境中将锁定 /users
资源。目前,我们创建了一个增删改查的功能,学习了如何创建 vue-router 并且异步获取数据。
我们还可以把axios客户端请求部分的代码再抽象出来,现在很简单。所以我们将在第四部分来讲解。当我们添加额外的API功能时,我们将创建一个专用的HTTP客户端模块。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。