> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Vue.js 入门 —— 与 Inertia × Laravel 搭配使用的基础

> 介绍对 Laravel 用户来说最熟悉的 JS 框架 Vue.js。从 Options API / Composition API 概览,到 Inertia v3 × Vue 3 中的页面组件、useForm、共享数据都会做实战讲解。

## 什么是 Vue.js

Vue.js(下文简称 Vue)是一款用于构建用户界面的渐进式 JavaScript 框架。“渐进式”指可以从很小的部分开始逐步加入更多能力,既能嵌入到已有 HTML 页面里,也能撑起一个大规模 SPA。

Vue 的核心是**响应式(reactivity)**。数据变化时 DOM 会自动更新,开发者不用手动管理“什么时候、更新哪个元素”。

<Info>
  本文讲解的是 Vue 3 与 Inertia v3 的组合。Laravel 13 的启动套件默认使用这一组合。
</Info>

### Options API 与 Composition API

Vue 3 提供两种编写组件的风格:**Options API** 和 **Composition API**。

**Options API** 是 Vue 2 一路沿用下来的传统风格,通过 `data`、`methods`、`computed`、`mounted` 等选项对象来定义组件。

```vue theme={null}
<!-- 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 也更契合。

```vue theme={null}
<!-- 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>
```

<Tip>
  在 Inertia × Laravel 的启动套件里,使用 `<script setup>` 的 Composition API 风格是标准。本文示例也都用 `<script setup>`。
</Tip>

***

## 在 Laravel 中的定位

### 历史

Vue 与 Laravel 的渊源很深,始于 **Laravel 5.3(2016 年)**,当时 Vue 被选为默认的前端框架。那时的 `package.json` 就包含了 Vue,并附带示例组件 `resources/js/components/ExampleComponent.vue`。

```mermaid theme={null}
timeline
    title Laravel 与 Vue 的历程
    2016 : Laravel 5.3 —— 默认采用 Vue
    2019 : Laravel 6 —— 把 Vue 脚手架分离到 laravel/ui 包
    2021 : Laravel 8 —— Jetstream + Inertia(Vue) 启动套件登场
    2022 : Laravel 9 —— 迁移到 Vite
    2025 : Laravel 12 —— 启动套件焕新(Vue / React)
    2026 : Laravel 13 —— 支持 Inertia v3 的启动套件
```

**Laravel 6(2019 年)** 把认证脚手架剥离到 `laravel/ui` 包,Vue 脚手架也随之搬到了这个包里。现在的主流方式,是通过 `laravel new` 的启动套件选择 Inertia + Vue。

对 Laravel 用户来说,Vue 是最熟悉的 JS 框架,中文学习资料也很丰富。

### 当前主流:Inertia × Vue

当前 Laravel 中使用 Vue 的主流方式就是 **Inertia × Vue**。Inertia 让你无需设计 API,就能直接从 Laravel 控制器把数据传给 Vue 组件,实现“现代单体”架构。

```mermaid theme={null}
graph LR
    Browser["浏览器"]
    Inertia["Inertia.js<br>(适配器层)"]
    Laravel["Laravel<br>(控制器)"]
    Vue["Vue<br>(页面组件)"]

    Browser <-->|XHR / 整页加载| Inertia
    Inertia <-->|Inertia 响应| Laravel
    Inertia -->|props| Vue
    Vue -->|渲染| Browser
```

***

## 搭建

### 通过启动套件(推荐)

新项目最省事的方法就是使用启动套件。

```shell theme={null}
laravel new my-app
```

在交互式提示中选择 **Vue**,以下内容会全部自动配好。

* `inertiajs/inertia-laravel`(服务端适配器)
* `@inertiajs/vue3`(客户端适配器)
* `vue`(Vue 3 本体)
* `@vitejs/plugin-vue`(Vite 插件)
* `HandleInertiaRequests` 中间件
* 登录 / 注册等认证页面(已用 Inertia + Vue 实现)

### 手动安装

在已有项目中引入时,分别安装服务端和客户端。

```shell theme={null}
# 服务端(PHP)
composer require inertiajs/inertia-laravel

# 客户端(JavaScript)
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
```

然后在 `vite.config.js` 里加上 Vue 插件。

```js theme={null}
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 应用。

```js theme={null}
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)
    },
})
```

<Info>
  手动安装的详细步骤(根模板设置、中间件注册等)请参考 [Inertia 官方文档](https://inertiajs.com/installation)。
</Info>

***

## 目录结构

启动套件把 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 值或表达式嵌入模板。

```vue theme={null}
<script setup>
const name = '世界'
const count = 3
</script>

<template>
    <p>你好,{{ name }}!</p>
    <p>2 倍是 {{ count * 2 }}</p>
</template>
```

#### `v-if` —— 条件分支

```vue theme={null}
<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` —— 列表渲染

```vue theme={null}
<template>
    <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
</template>
```

为了高效差异更新,一定要指定 `:key`。相当于 React 的 `Array.map()`。

#### `v-model` —— 双向绑定

`v-model` 可以把表单元素和响应式变量双向同步。

```vue theme={null}
<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` 的简写)

```vue theme={null}
<template>
    <!-- 动态属性绑定 -->
    <img :src="imageUrl" :alt="imageAlt" />

    <!-- 事件处理 -->
    <button @click="handleClick">点击</button>
    <form @submit.prevent="handleSubmit">...</form>
</template>
```

***

## 页面组件基础

Inertia 的页面组件就是普通的 Vue 组件。从 Laravel 控制器传来的数据会作为 props 接收。

### 控制器

```php theme={null}
// 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 页面组件

```vue theme={null}
<!-- 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 完成,避免整页重载。

```vue theme={null}
<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 指定 `action` 和 `method`,再用 `v-slot` 访问 `errors` 和 `processing`。

### 基本用法

```vue theme={null}
<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](/zh/blog/wayfinder-introduction) 把路由作为对象管理。`store.form()` 返回包含路由对象 `action` 与 `method` 的对象,用 `v-bind` 展开到 `<Form>`。

```vue theme={null}
<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` 中列出的字段会在提交成功后自动重置。密码等提交完希望清空的字段可以列在这里。

<Info>
  不用 Wayfinder 时,直接传 `action="/login"` 就能达到同样效果。
</Info>

***

## `useForm` helper

表单处理使用 `@inertiajs/vue3` 的 `useForm` helper,可以简洁实现表单状态管理、提交、校验错误显示。

### 控制器侧

```php theme={null}
// 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 表单组件

```vue theme={null}
<!-- 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()` 方法中定义。

```php theme={null}
// 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()` 访问共享数据。

```vue theme={null}
<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>
```

<Info>
  共享数据会包含在每一次请求中,建议只保留必要的最少数据。用 `fn()` 做惰性求值时,只有真正访问时才求值。
</Info>

***

## Vue 3 响应式基础

Inertia × Vue 开发中你需要了解的 Vue 3 响应式 API。

### `ref` —— 基础类型的响应式值

```vue theme={null}
<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` —— 计算属性

```vue theme={null}
<script setup>
import { ref, computed } from 'vue'

const posts = ref([])

const publishedPosts = computed(() =>
    posts.value.filter(post => post.published)
)
</script>
```

### `onMounted` —— 挂载后的处理

```vue theme={null}
<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。用启动套件创建项目后,连认证页面都是现成的,可以立即开始开发。

<Card title="Inertia.js 官方文档" icon="book-open" href="https://inertiajs.com">
  Inertia v3 的完整功能请参考官方文档。
</Card>


## Related topics

- [Svelte 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/svelte-introduction.md)
- [React 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/react-introduction.md)
- [前端](/zh/frontend.md)
- [启动套件](/zh/starter-kits.md)
- [开始学习 Laravel 前需要具备的知识](/zh/true-tutorial.md)
