使用 Laravel 构建 Vue SPA 第 3 部分

发布时间 作者

Building a Vue SPA with Laravel Part 3 image

我们将继续构建我们的 Vue SPA 与 Laravel,向您展示如何在 vue-router 进入路由之前加载异步数据。

我们在 使用 Laravel 构建 Vue SPA 第 2 部分 中结束了一个 UsersIndex Vue 组件,它从 API 异步加载用户。我们省去了构建由数据库支持的真实 API,而是选择了在 Laravel 的 factory() 方法中从 API 响应获取虚拟数据。

如果您还没有阅读使用 Laravel 构建 Vue SPA 的第 1 部分第 2 部分,我建议您先从这些文章开始阅读,然后再回来。我会在这里等您的!

在本教程中,我们还将用一个由数据库支持的真实端点替换我们的虚拟 /users 端点。我更喜欢使用 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,从中使用 Laravel 在 Laravel 5.5 中引入的新 API 资源 返回 JSON 数据。

在我们创建控制器和 API 资源之前,让我们先设置一个数据库和 seeder 来为我们的 SPA 提供一些测试数据。

用户数据库 seeder

我们可以使用 make:seeder 命令创建一个新的用户 seeder

php artisan make:seeder UsersTableSeeder

UsersTableSeeder 现在非常简单 - 我们只使用模型工厂创建 50 个用户

<?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,
]);
}
}

在创建和配置数据库之前,我们无法应用此 seeder。

配置数据库

现在是将我们的 Vue SPA Laravel 应用程序连接到真实数据库的时候了。您可以使用 SQLite 以及像 TablePlus 这样的 GUI 或 MySQL。如果您是 Laravel 的新手,可以参考有关数据库入门的详细文档。

如果您在您的机器上运行本地 MySQL 实例,则可以使用以下命令从命令行快速创建新的数据库(假设您没有本地开发的密码)

mysql -u root -e"create database vue_spa;"
 
# or you could prompt for the password with the -p flag
mysql -u root -e"create database vue_spa;" -p

拥有数据库后,请在 .env 文件中配置 DB_DATABASE=vue_spa。如果您遇到问题,请参考文档,这将使您的数据库轻松运行。

配置好数据库连接后,您可以迁移数据库表并添加种子数据。Laravel 附带一个 Users 表迁移,我们使用它来播种数据

# Ensure the database seeders get auto-loaded
composer dump-autoload
php artisan migrate:fresh --seed

您也可以使用单独的 artisan db:seed 命令!就是这样;您应该有一个包含 50 个用户的数据库,我们可以通过 API 查询和返回这些用户。

用户控制器

如果您还记得第 2 部分,routes/api.php 文件中虚拟的 /users 端点如下所示

Route::get('/users', function () {
return factory('App\User', 10)->make();
});

让我们创建一个控制器类,这还让我们能够在生产环境中使用 php artisan route:cache,而闭包则不能。我们将从命令行创建控制器和 User API 资源类

php artisan make:controller Api/UsersController
php artisan make:resource UserResource

第一个命令在 app/Http/Controllers/Api 中的 Api 文件夹中添加 User 控制器,第二个命令将 UserResource 添加到 app/Http/Resources 文件夹中。

以下是我们控制器和 Api 命名空间的新的 routes/api.php 代码

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));
}
}

以下是一个 JSON 响应的示例,一旦我们将 UserResource 连接到 API 格式,它就会显示出来

{
"data":[
{
"name":"Francis Marquardt",
},
{
"name":"Dr. Florine Beatty",
},
...
],
"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 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 组件

我们可以通过调整 then() 调用来快速使我们的 UsersIndex.vue 组件再次工作,以便引用我们的用户数据所在的 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 保护来实现这一点。来自vue-router 文档 的一个示例如下所示

beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},

查看文档以获取完整的示例,但总而言之,我们将异步获取用户数据,完成后,只有在完成后,我们才会触发 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,因此样式略有不同

// 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`、`links` 和 `meta` 键。我们使用 `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>

如果更容易理解,这里有UsersIndex.vue 作为 GitHub Gist

这里有许多新内容,因此我将指出一些比较重要的点。 `goToNext()` 和 `goToPrev()` 方法演示了如何使用 `this.$router.push` 使用 `vue-router` 进行导航

this.$router.push({
query: {
page: `${this.nextPage}`,
},
});

我们正在向查询字符串推送一个新页面,这将触发 `beforeRouteUpdate`。我还想指出,我向您展示了一个 `

Paul Redmond photo

Laravel News 的特约作者。全栈 Web 开发人员和作者。

归档于
Cube

Laravel 新闻

加入 40k+ 其他开发者,绝不错过任何新提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

绝不妥协

Joel 和 Aaron,来自“绝不妥协”播客的两位经验丰富的开发者,现在可以为您的 Laravel 项目提供服务。 ⬧ 固定费用 7500 美元/月。 ⬧ 没有冗长的销售流程。 ⬧ 无需签订合同。 ⬧ 100% 退款保证。

绝不妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧版本的 Laravel?即时、自动化的 Laravel 升级和代码现代化,使您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

让您的项目充满活力,只需每月 2500 美元,即可获得 4-6 年经验的经验丰富的 Laravel 开发人员。获得 160 小时的专用专业知识和 15 天的免费试用。立即安排通话!

Bacancy
Lucky Media logo

Lucky Media

立即获得幸运 - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar:Laravel 电子商务

Laravel 的电子商务。一个开源包,将现代无头电子商务功能的强大功能带到 Laravel。

Lunar:Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit:Laravel SaaS 启动工具包

SaaSykit 是一款 Laravel SaaS 启动工具包,包含运行现代 SaaS 所需的所有功能。付款、精美结账、管理面板、用户仪表板、身份验证、现成组件、统计信息、博客、文档等等。

SaaSykit:Laravel SaaS 启动工具包
Rector logo

Rector

您的无缝 Laravel 升级合作伙伴,降低成本,加速创新,帮助企业取得成功

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖项输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章