关于 laravel-inertia-react
面向使用 Laravel、Inertia.js 和 React 构建全栈应用的开发者,提供页面、表单、导航、共享数据、布局及上传方面的规则和示例。
主要功能
- 01带类型的 Inertia 页面组件
- 02使用 useForm 处理表单
- 03导航与部分重新加载
- 04共享数据与身份验证
- 05持久化及嵌套布局
- 06文件上传模式
适用场景
- 01构建 React Inertia 页面
- 02处理 Laravel 表单验证错误
- 03共享已验证用户数据
- 04添加带进度跟踪的文件上传
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ asyrafhussin/ agent-skills --skill laravel-inertia-react安装后在 Claude Code 中输入/laravel-inertia-react确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonREADME.mdrules/_sections.md_template.mdform-dirty-tracking.mdform-file-uploads.mdform-progress-indicator.mdform-reset-handling.mdform-transform-data.mdform-useform-basic.mdform-useform-hook.mdform-validation-errors.mdlayout-persistent.mdnav-external-links.mdnav-link-component.mdnav-preserve-state.mdnav-programmatic.mdnav-replace-history.mdpage-component-structure.mdpage-default-layout.mdpage-head-management.mdpage-partial-reloads.mdpage-props-typing.mdpage-scroll-preservation.mdshared-app-config.mdshared-auth-user.mdshared-flash-messages.mdshared-ziggy-routes.md详细介绍
Laravel + Inertia.js + React
使用 Laravel、Inertia.js 和 React 构建现代单体应用的全面模式。包含 30 多条规则,助力流畅的全栈开发。
适用场景
在以下情况下参考这些指南:
- 创建 Inertia 页面组件
- 使用 useForm hook 处理表单
- 管理共享数据和身份验证
- 实现持久化布局
- 在页面间导航
按优先级划分的规则类别
| 优先级 | 类别 | 影响 | 前缀 |
|---|---|---|---|
| 1 | 页面组件 | 严重 | page- |
| 2 | 表单与验证 | 严重 | form- |
| 3 | 导航与链接 | 高 | nav- |
| 4 | 共享数据 | 高 | shared- |
| 5 | 布局 | 中 | layout- |
| 6 | 文件上传 | 中 | upload- |
| 7 | 高级模式 | 低 | advanced- |
快速参考
1. 页面组件(严重)
page-props-typing- 为 Laravel 传入的页面 props 添加类型page-component-structure- 标准页面组件模式page-head-management- 使用 Head 设置标题和 meta 标签page-default-layout- 为页面指定布局
2. 表单与验证(严重)
form-useform-basic- useForm 的基本用法form-validation-errors- 显示 Laravel 验证错误form-processing-state- 处理表单提交状态form-reset-preserve- 重置或保留表单数据form-nested-data- 处理嵌套表单数据form-transform- 提交前转换数据
3. 导航与链接(高)
nav-link-component- 使用 Link 进行导航nav-preserve-state- 保留滚动位置和状态nav-partial-reloads- 仅重新加载发生变化的内容nav-replace-history- 替换或推入历史记录
4. 共享数据(高)
shared-auth-user- 访问已通过身份验证的用户shared-flash-messages- 处理闪存消息shared-global-props- 访问全局 propsshared-typescript- 为共享数据添加类型
5. 布局(中)
layout-persistent- 持久化布局模式layout-nested- 嵌套布局layout-default- 默认布局分配layout-conditional- 条件布局
6. 文件上传(中)
upload-basic- 基本文件上传upload-progress- 跟踪上传进度upload-multiple- 上传多个文件
7. 高级模式(低)
advanced-polling- 实时轮询advanced-prefetch- 预取页面advanced-modal-pages- 将模态框用作页面advanced-infinite-scroll- 无限滚动
基本模式
使用 TypeScript 的页面组件
// resources/js/Pages/Posts/Index.tsx
import { Head, Link } from '@inertiajs/react'
interface Post {
id: number
title: string
excerpt: string
created_at: string
author: {
id: number
name: string
}
}
interface Props {
posts: {
data: Post[]
links: { url: string | null; label: string; active: boolean }[]
}
filters: {
search?: string
}
}
export default function Index({ posts, filters }: Props) {
return (
<>
<Head title="Posts" />
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Posts</h1>
<div className="space-y-4">
{posts.data.map((post) => (
<article key={post.id} className="p-4 bg-white rounded-lg shadow">
<Link href={route('posts.show', post.id)}>
<h2 className="text-xl font-semibold hover:text-blue-600">
{post.title}
</h2>
</Link>
<p className="text-gray-600 mt-2">{post.excerpt}</p>
<p className="text-sm text-gray-400 mt-2">
By {post.author.name}
</p>
</article>
))}
</div>
</div>
</>
)
}
使用 useForm 的表单
// resources/js/Pages/Posts/Create.tsx
import { Head, useForm, Link } from '@inertiajs/react'
import { FormEvent } from 'react'
interface Category {
id: number
name: string
}
interface Props {
categories: Category[]
}
export default function Create({ categories }: Props) {
const { data, setData, post, processing, errors, reset } = useForm({
title: '',
body: '',
category_id: '',
})
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
post(route('posts.store'), {
onSuccess: () => reset(),
})
}
return (
<>
<Head title="Create Post" />
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto py-8">
<div className="mb-4">
<label htmlFor="title" className="block font-medium mb-1">
Title
</label>
<input
id="title"
type="text"
value={data.title}
onChange={(e) => setData('title', e.target.value)}
className="w-full border rounded px-3 py-2"
/>
{errors.title && (
<p className="text-red-500 text-sm mt-1">{errors.title}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="category" className="block font-medium mb-1">
Category
</label>
<select
id="category"
value={data.category_id}
onChange={(e) => setData('category_id', e.target.value)}
className="w-full border rounded px-3 py-2"
>
<option value="">Select a category</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.category_id && (
<p className="text-red-500 text-sm mt-1">{errors.category_id}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="body" className="block font-medium mb-1">
Content
</label>
<textarea
id="body"
value={data.body}
onChange={(e) => setData('body', e.target.value)}
rows={10}
className="w-full border rounded px-3 py-2"
/>
{errors.body && (
<p className="text-red-500 text-sm mt-1">{errors.body}</p>
)}
</div>
<div className="flex gap-4">
<button
type="submit"
disabled={processing}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{processing ? 'Creating...' : 'Create Post'}
</button>
<Link
href={route('posts.index')}
className="px-4 py-2 border rounded"
>
Cancel
</Link>
</div>
</form>
</>
)
}
持久化布局
// resources/js/Layouts/AppLayout.tsx
import { Link, usePage } from '@inertiajs/react'
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export default function AppLayout({ children }: Props) {
const { auth } = usePage().props as { auth: { user: { name: string } } }
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="container mx-auto px-4 py-3 flex justify-between">
<Link href="/" className="font-bold">
My App
</Link>
<span>Welcome, {auth.user.name}</span>
</div>
</nav>
<main className="container mx-auto px-4 py-8">
{children}
</main>
</div>
)
}
// resources/js/Pages/Dashboard.tsx
import AppLayout from '@/Layouts/AppLayout'
export default function Dashboard() {
return <h1>Dashboard</h1>
}
// Assign persistent layout
Dashboard.layout = (page: ReactNode) => <AppLayout>{page}</AppLayout>
Laravel 控制器
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::with('author:id,name')
->latest()
->paginate(10),
'filters' => request()->only('search'),
]);
}
public function create(): Response
{
return Inertia::render('Posts/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create([
...$request->validated(),
'user_id' => auth()->id(),
]);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function show(Post $post): Response
{
return Inertia::render('Posts/Show', [
'post' => $post->load('author', 'category'),
]);
}
}
共享数据(HandleInertiaRequests)
<?php
namespace App\Http\Middleware;
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() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),
'error' => $request->session()->get('error'),
],
]);
}
}
闪存消息组件
// resources/js/Components/FlashMessages.tsx
import { usePage } from '@inertiajs/react'
import { useEffect, useState } from 'react'
export default function FlashMessages() {
const { flash } = usePage().props as {
flash: { success?: string; error?: string }
}
const [visible, setVisible] = useState(false)
useEffect(() => {
if (flash.success || flash.error) {
setVisible(true)
const timer = setTimeout(() => setVisible(false), 3000)
return () => clearTimeout(timer)
}
}, [flash])
if (!visible) return null
return (
<div className="fixed top-4 right-4 z-50">
{flash.success && (
<div className="bg-green-500 text-white px-4 py-2 rounded shadow">
{flash.success}
</div>
)}
{flash.error && (
<div className="bg-red-500 text-white px-4 py-2 rounded shadow">
{flash.error}
</div>
)}
</div>
)
}
使用方法
阅读各个规则文件,了解详细说明和代码示例:
rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.md
项目结构
laravel-inertia-react/
├── SKILL.md # This file - overview and examples
├── README.md # Quick reference guide
├── AGENTS.md # Integration guide for AI agents
├── metadata.json # Skill metadata and references
└── rules/
├── _sections.md # Rule categories and priorities
├── _template.md # Template for new rules
├── page-*.md # Page component patterns (6 rules)
├── form-*.md # Form handling patterns (8 rules)
├── nav-*.md # Navigation patterns (5 rules)
├── shared-*.md # Shared data patterns (4 rules)
└── layout-*.md # Layout patterns (1 rule)
参考资料
- Inertia.js 文档 - Inertia.js 官方文档
- Laravel 文档 - Laravel 框架文档
- React 文档 - React 官方文档
- Ziggy - Laravel 的 JavaScript 路由辅助工具
许可证
MIT 许可证。本技能按原样提供,用于教育和开发目的。
元数据
- 版本:1.0.1
- 最后更新:2026-01-17
- 维护者:Asyraf Hussin
- 规则数量:6 个类别中的 24 条规则
- 技术栈:Laravel 10+、Inertia.js 1.0+、React 18+、TypeScript 5+
评论
支持 Markdown