跳转到主要内容

什么是 Vue.js

Vue.js(下文简称 Vue)是一款用于构建用户界面的渐进式 JavaScript 框架。“渐进式”指可以从很小的部分开始逐步加入更多能力,既能嵌入到已有 HTML 页面里,也能撑起一个大规模 SPA。 Vue 的核心是响应式(reactivity)。数据变化时 DOM 会自动更新,开发者不用手动管理“什么时候、更新哪个元素”。
本文讲解的是 Vue 3 与 Inertia v3 的组合。Laravel 13 的启动套件默认使用这一组合。

Options API 与 Composition API

Vue 3 提供两种编写组件的风格:Options APIComposition API Options API 是 Vue 2 一路沿用下来的传统风格,通过 datamethodscomputedmounted 等选项对象来定义组件。
<!-- 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 组件,实现“现代单体”架构。

搭建

通过启动套件(推荐)

新项目最省事的方法就是使用启动套件。
laravel new my-app
在交互式提示中选择 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)
    },
})
手动安装的详细步骤(根模板设置、中间件注册等)请参考 Inertia 官方文档

目录结构

启动套件把 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。
使用 @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。

Form 组件

@inertiajs/vue3 提供的 <Form> 组件是启动套件认证页面推荐的表单提交样式。通过 props 指定 actionmethod,再用 v-slot 访问 errorsprocessing

基本用法

<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() 返回包含路由对象 actionmethod 的对象,用 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" 就能达到同样效果。

useForm helper

表单处理使用 @inertiajs/vue3useForm 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 的完整功能请参考官方文档。
最后修改于 2026年7月13日