mcpField
L
laravel-inertia-react

laravel-inertia-react

github.com/asyrafhussin开发952.0k 次安装9 周前更新
代码仓库

关于 laravel-inertia-react

面向使用 Laravel、Inertia.js 和 React 构建全栈应用的开发者,提供页面、表单、导航、共享数据、布局及上传方面的规则和示例。

主要功能

  1. 01带类型的 Inertia 页面组件
  2. 02使用 useForm 处理表单
  3. 03导航与部分重新加载
  4. 04共享数据与身份验证
  5. 05持久化及嵌套布局
  6. 06文件上传模式

适用场景

  1. 01构建 React Inertia 页面
  2. 02处理 Laravel 表单验证错误
  3. 03共享已验证用户数据
  4. 04添加带进度跟踪的文件上传

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/asyrafhussin/agent-skills --skill laravel-inertia-react

安装后在 Claude Code 中输入/laravel-inertia-react确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
metadata.json
README.md
rules/
_sections.md
_template.md
form-dirty-tracking.md
form-file-uploads.md
form-progress-indicator.md
form-reset-handling.md
form-transform-data.md
form-useform-basic.md
form-useform-hook.md
form-validation-errors.md
layout-persistent.md
nav-external-links.md
nav-link-component.md
nav-preserve-state.md
nav-programmatic.md
nav-replace-history.md
page-component-structure.md
page-default-layout.md
page-head-management.md
page-partial-reloads.md
page-props-typing.md
page-scroll-preservation.md
shared-app-config.md
shared-auth-user.md
shared-flash-messages.md
shared-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 - 访问全局 props
  • shared-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)

参考资料

许可证

MIT 许可证。本技能按原样提供,用于教育和开发目的。

元数据

  • 版本:1.0.1
  • 最后更新:2026-01-17
  • 维护者:Asyraf Hussin
  • 规则数量:6 个类别中的 24 条规则
  • 技术栈:Laravel 10+、Inertia.js 1.0+、React 18+、TypeScript 5+

评论

支持 Markdown