什么是 Vue.js
Vue.js(下文简称 Vue)是一款用于构建用户界面的渐进式 JavaScript 框架。“渐进式”指可以从很小的部分开始逐步加入更多能力,既能嵌入到已有 HTML 页面里,也能撑起一个大规模 SPA。
Vue 的核心是响应式(reactivity) 。数据变化时 DOM 会自动更新,开发者不用手动管理“什么时候、更新哪个元素”。
本文讲解的是 Vue 3 与 Inertia v3 的组合。Laravel 13 的启动套件默认使用这一组合。
Options API 与 Composition API
Vue 3 提供两种编写组件的风格:Options API 和 Composition API 。
Options API 是 Vue 2 一路沿用下来的传统风格,通过 data、methods、computed、mounted 等选项对象来定义组件。
<!-- Options API 示例 -->
< script >
export default {
data () {
return { count: 0 }
} ,
methods: {
increment () {
this . count ++
}
}
}
</ script >
< template >
< button @ click = " increment " > {{ count }} </ button >
</ template >
Composition API 是 Vue 3 引入的新风格。搭配 <script setup> 语法后,可以写得更简洁。逻辑复用性更好,与 TypeScript 也更契合。
<!-- Composition API(<script setup>)示例 -->
< script setup >
import { ref } from 'vue'
const count = ref ( 0 )
function increment () {
count . value ++
}
</ script >
< template >
< button @ click = " increment " > {{ count }} </ button >
</ template >
在 Inertia × Laravel 的启动套件里,使用 <script setup> 的 Composition API 风格是标准。本文示例也都用 <script setup>。
在 Laravel 中的定位
Vue 与 Laravel 的渊源很深,始于 Laravel 5.3(2016 年) ,当时 Vue 被选为默认的前端框架。那时的 package.json 就包含了 Vue,并附带示例组件 resources/js/components/ExampleComponent.vue。
Laravel 6(2019 年) 把认证脚手架剥离到 laravel/ui 包,Vue 脚手架也随之搬到了这个包里。现在的主流方式,是通过 laravel new 的启动套件选择 Inertia + Vue。
对 Laravel 用户来说,Vue 是最熟悉的 JS 框架,中文学习资料也很丰富。
当前主流:Inertia × Vue
当前 Laravel 中使用 Vue 的主流方式就是 Inertia × Vue 。Inertia 让你无需设计 API,就能直接从 Laravel 控制器把数据传给 Vue 组件,实现“现代单体”架构。
通过启动套件(推荐)
新项目最省事的方法就是使用启动套件。
在交互式提示中选择 Vue ,以下内容会全部自动配好。
inertiajs/inertia-laravel(服务端适配器)
@inertiajs/vue3(客户端适配器)
vue(Vue 3 本体)
@vitejs/plugin-vue(Vite 插件)
HandleInertiaRequests 中间件
登录 / 注册等认证页面(已用 Inertia + Vue 实现)
手动安装
在已有项目中引入时,分别安装服务端和客户端。
# 服务端(PHP)
composer require inertiajs/inertia-laravel
# 客户端(JavaScript)
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
然后在 vite.config.js 里加上 Vue 插件。
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue'
export default defineConfig ({
plugins: [
laravel ({
input: [ 'resources/css/app.css' , 'resources/js/app.js' ],
refresh: true ,
}),
vue ({
template: {
transformAssetUrls: {
base: null ,
includeAbsolute: false ,
},
},
}),
] ,
})
在 resources/js/app.js 里启动 Inertia 应用。
import { createApp , h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
createInertiaApp ({
resolve : ( name ) =>
resolvePageComponent (
`./pages/ ${ name } .vue` ,
import . meta . glob ( './pages/**/*.vue' ),
),
setup ({ el , App , props , plugin }) {
createApp ({ render : () => h ( App , props ) })
. use ( plugin )
. mount ( el )
},
})
目录结构
启动套件把 Vue 页面组件放在 resources/js/pages/ 目录下。
resources/js/
├── app.js # Inertia 应用入口
├── bootstrap.js
├── components/ # 可复用 UI 组件
│ ├── NavBar.vue
│ └── ...
├── layouts/ # 布局组件
│ ├── AppLayout.vue
│ └── AuthLayout.vue
└── pages/ # Inertia 页面组件(对应控制器名)
├── Auth/
│ ├── Login.vue
│ └── Register.vue
├── Dashboard.vue
└── Posts/
├── Index.vue
├── Create.vue
└── Show.vue
写 Inertia::render('Posts/Index', [...]) 时对应的就是 resources/js/pages/Posts/Index.vue。
Vue 模板语法
读懂和编写启动套件代码所需的基本模板指令。
{{ }} —— 变量展开
用双大括号把 JavaScript 值或表达式嵌入模板。
< script setup >
const name = '世界'
const count = 3
</ script >
< template >
< p > 你好,{{ name }}! </ p >
< p > 2 倍是 {{ count * 2 }} </ p >
</ template >
v-if —— 条件分支
< template >
< p v-if = " isLoggedIn " > 欢迎! </ p >
< p v-else-if = " role === 'admin' " > 正在以管理员身份登录 </ p >
< a v-else href = "/login" > 登录 </ a >
</ template >
相当于 Svelte 的 {#if} 或 React 中的三元运算符。
v-for —— 列表渲染
< template >
< ul >
< li v-for = " post in posts " : key = " post . id " > {{ post . title }} </ li >
</ ul >
</ template >
为了高效差异更新,一定要指定 :key。相当于 React 的 Array.map()。
v-model —— 双向绑定
v-model 可以把表单元素和响应式变量双向同步。
< script setup >
import { ref } from 'vue'
const title = ref ( '' )
const agreed = ref ( false )
const role = ref ( 'viewer' )
</ script >
< template >
<!-- 文本输入 -->
< input v-model = " title " type = "text" />
< p > 输入中: {{ title }} </ p >
<!-- 复选框 -->
< input v-model = " agreed " type = "checkbox" />
< p > 同意: {{ agreed }} </ p >
<!-- 下拉框 -->
< select v-model = " role " >
< option value = "viewer" > 查看者 </ option >
< option value = "editor" > 编辑者 </ option >
< option value = "admin" > 管理员 </ option >
</ select >
</ template >
:(v-bind)与 @(v-on)
:attr="value" —— 给 HTML 属性做动态绑定(v-bind:attr 的简写)
@event="handler" —— 注册事件监听器(v-on:event 的简写)
< template >
<!-- 动态属性绑定 -->
< img : src = " imageUrl " : alt = " imageAlt " />
<!-- 事件处理 -->
< button @ click = " handleClick " > 点击 </ button >
< form @ submit . prevent = " handleSubmit " > ... </ form >
</ template >
页面组件基础
Inertia 的页面组件就是普通的 Vue 组件。从 Laravel 控制器传来的数据会作为 props 接收。
控制器
// app/Http/Controllers/PostController.php
use Inertia\ Inertia ;
use App\Models\ Post ;
class PostController extends Controller
{
public function index ()
{
return Inertia :: render ( 'Posts/Index' , [
'posts' => Post :: latest () -> paginate ( 10 ),
]);
}
}
Vue 页面组件
<!-- resources/js/pages/Posts/Index.vue -->
< script setup >
import { Link } from '@inertiajs/vue3'
defineProps ({
posts: Object ,
})
</ script >
< template >
< div >
< h1 > 文章列表 </ h1 >
< article v-for = " post in posts . data " : key = " post . id " >
< h2 >
< Link : href = " `/posts/ ${ post . id } ` " > {{ post . title }} </ Link >
</ h2 >
< p > {{ post . created_at }} </ p >
</ article >
</ div >
</ template >
只要用 defineProps() 声明 props,就能在模板中使用从控制器传来的数据。无需定义 REST API。
Link 组件
使用 @inertiajs/vue3 提供的 <Link> 组件,页面跳转通过 XHR 完成,避免整页重载。
< script setup >
import { Link } from '@inertiajs/vue3'
</ script >
< template >
<!-- 基础链接 -->
< Link href = "/posts" > 文章列表 </ Link >
<!-- 用 POST 方法的链接(如删除) -->
< Link href = "/posts/1" method = "delete" as = "button" type = "button" >
删除
</ Link >
<!-- 预加载(hover 时预取) -->
< Link href = "/posts/1" preload > 查看文章 </ Link >
</ template >
写法和普通 <a> 一样,但幕后 Inertia 只替换页面组件,体验接近 SPA。
@inertiajs/vue3 提供的 <Form> 组件是启动套件认证页面推荐的表单提交样式。通过 props 指定 action 和 method,再用 v-slot 访问 errors 和 processing。
基本用法
< script setup >
import { Form } from '@inertiajs/vue3'
</ script >
< template >
< Form action = "/posts" method = "post" class = "flex flex-col gap-4" v-slot = " { errors , processing } " >
< div >
< label for = "title" > 标题 </ label >
< input id = "title" name = "title" type = "text" required />
< p v-if = " errors . title " class = "error" > {{ errors . title }} </ p >
</ div >
< div >
< label for = "content" > 正文 </ label >
< textarea id = "content" name = "content" ></ textarea >
< p v-if = " errors . content " class = "error" > {{ errors . content }} </ p >
</ div >
< button type = "submit" : disabled = " processing " >
{{ processing ? '提交中...' : '发布' }}
</ button >
</ Form >
</ template >
v-slot="{ errors, processing }" 是 Vue 的作用域插槽语法,Form 组件会自动算出这些值并传入。表单字段用 HTML 原生的 name 属性而不是 v-model,让浏览器的标准表单数据采集照常工作。
启动套件的模式
启动套件通过 Wayfinder 把路由作为对象管理。store.form() 返回包含路由对象 action 与 method 的对象,用 v-bind 展开到 <Form>。
< script setup lang = "ts" >
import { Form } from '@inertiajs/vue3'
import { store } from '@/routes/login'
</ script >
< template >
< Form
v-bind = " store . form () "
: reset-on-success = " [ 'password' ] "
v-slot = " { errors , processing } "
class = "flex flex-col gap-6"
>
<!-- 表单内容 -->
</ Form >
</ template >
reset-on-success 中列出的字段会在提交成功后自动重置。密码等提交完希望清空的字段可以列在这里。
不用 Wayfinder 时,直接传 action="/login" 就能达到同样效果。
表单处理使用 @inertiajs/vue3 的 useForm helper,可以简洁实现表单状态管理、提交、校验错误显示。
控制器侧
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
public function store ( Request $request )
{
$validated = $request -> validate ([
'title' => [ 'required' , 'string' , 'max:255' ],
'content' => [ 'required' , 'string' ],
]);
Post :: create ( $validated + [ 'user_id' => auth () -> id ()]);
return redirect () -> route ( 'posts.index' )
-> with ( 'success' , '文章已创建。' );
}
}
Vue 表单组件
<!-- resources/js/pages/Posts/Create.vue -->
< script setup >
import { useForm } from '@inertiajs/vue3'
const form = useForm ({
title: '' ,
content: '' ,
})
function submit () {
form . post ( '/posts' )
}
</ script >
< template >
< form @ submit . prevent = " submit " >
< div >
< label > 标题 </ label >
< input v-model = " form . title " type = "text" />
< p v-if = " form . errors . title " class = "error" > {{ form . errors . title }} </ p >
</ div >
< div >
< label > 正文 </ label >
< textarea v-model = " form . content " ></ textarea >
< p v-if = " form . errors . content " class = "error" > {{ form . errors . content }} </ p >
</ div >
< button type = "submit" : disabled = " form . processing " >
{{ form . processing ? '提交中...' : '发布' }}
</ button >
</ form >
</ template >
useForm 返回对象的主要属性:
属性 / 方法 说明 form.data表单数据对象 form.errors校验错误(按字段名访问) form.processing提交中为 true(用来禁用按钮) form.isDirty相对初始值发生变化时为 true form.post(url)以 POST 提交 form.put(url)以 PUT 提交(更新) form.delete(url)以 DELETE 提交 form.reset()表单重置为初始值
校验返回错误时,useForm 会保留已输入内容并显示错误。配合 v-model 可以实现顺滑的表单体验。
共享数据(Shared Data)
所有页面共用的数据(登录中的用户信息、闪存消息等)可以在 HandleInertiaRequests 中间件的 share() 方法中定义。
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\ Request ;
use Inertia\ Middleware ;
class HandleInertiaRequests extends Middleware
{
public function share ( Request $request ) : array
{
return array_merge ( parent :: share ( $request ), [
'auth' => [
'user' => $request -> user ()
? $request -> user () -> only ( 'id' , 'name' , 'email' )
: null ,
],
'flash' => [
'success' => $request -> session () -> get ( 'success' ),
'error' => $request -> session () -> get ( 'error' ),
],
]);
}
}
在 Vue 组件中通过 usePage() 访问共享数据。
< script setup >
import { computed } from 'vue'
import { usePage } from '@inertiajs/vue3'
const page = usePage ()
// 访问共享数据
const user = computed (() => page . props . auth . user )
const flash = computed (() => page . props . flash )
</ script >
< template >
< header >
< span v-if = " user " > {{ user . name }} </ span >
< span v-else > 游客 </ span >
</ header >
< div v-if = " flash . success " class = "alert-success" >
{{ flash . success }}
</ div >
</ template >
共享数据会包含在每一次请求中,建议只保留必要的最少数据。用 fn() 做惰性求值时,只有真正访问时才求值。
Vue 3 响应式基础
Inertia × Vue 开发中你需要了解的 Vue 3 响应式 API。
ref —— 基础类型的响应式值
< script setup >
import { ref } from 'vue'
const count = ref ( 0 )
const isOpen = ref ( false )
// 通过 .value 访问(模板中不需要)
count . value ++
</ script >
< template >
< p > {{ count }} </ p >
< button @ click = " isOpen = ! isOpen " > 切换 </ button >
</ template >
computed —— 计算属性
< script setup >
import { ref , computed } from 'vue'
const posts = ref ([])
const publishedPosts = computed (() =>
posts . value . filter ( post => post . published )
)
</ script >
onMounted —— 挂载后的处理
< script setup >
import { onMounted } from 'vue'
onMounted (() => {
console . log ( '组件已挂载' )
})
</ script >
Vue.js 与 Laravel 兼容性很高,特别是通过 Inertia 采用“现代单体”结构时会充分发挥它的价值。
要素 作用 Laravel 控制器 路由、数据获取、校验 Inertia::render()从控制器把数据传给 Vue 组件 Vue 页面组件 接收 props 渲染 UI useForm表单状态管理、提交、错误展示 Link 组件无需整页刷新的页面跳转 usePage().props访问共享数据
用 Inertia × Vue,你可以同时享受到 Laravel 后端的简洁和 Vue 的响应式 UI。用启动套件创建项目后,连认证页面都是现成的,可以立即开始开发。
Inertia.js 官方文档 Inertia v3 的完整功能请参考官方文档。