前端开发
在 Inertia Rails + React 中把…
当你在 Inertia.js + Rails + React 项目中使用 shadcn/ui,或从 Next.js 示例迁移 shadcn 组件时,按本经验替换 Next.js 专用依赖并接通 Inertia 导航、表单、Flash Toast 与暗色模式。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Inertia Rails + Re特定
解决思路
- 使用前先审查 shadcn 示例:若使用 `react-hook-form` + `zod`,改为 Inertia `<Form>` 组件并为输入控件添加 `name` 属性;让 Inertia 处理 CSRF、错误、重定向和处理中状态,不要用 react-hook-form 对抗这些机制。
- 若示例包含 `'use client'`,删除它;Inertia 没有 RSC,所有组件都是客户端组件。
- 若示例使用 `next/link`、`next/head`、`useRouter()`,分别替换为来自 `@inertiajs/react` 的 Inertia `<Link>`、`<Head>`、`router`。
- 对照替换 shadcn 的 Next.js 默认写法:`'use client'` 指令 -> 删除;`react-hook-form` + `zod` -> Inertia `<Form>`;`FormField`/`FormItem`/`FormMessage` -> 普通 `<Input name='...'>` + `errors.field`;`next-themes` -> CSS class 策略 + `@custom-variant`;Next 的 `useRouter()` -> 从 `@inertiajs/react` 导入 `router`;`next/link` -> 从 `@inertiajs/react` 导入 `<Link>`;`next/head` -> 从 `@inertiajs/react` 导入 `<Head>`。
- 绝不使用 shadcn 的 `FormField`、`FormItem`、`FormLabel`、`FormMessage` 组件;它们内部依赖 react-hook-form 的 `useFormContext`,没有 react-hook-form 会崩溃。
- 在 Inertia `<Form>` 内使用普通 shadcn `Input`/`Label`/`Select` 并设置 `name` 属性,从 render 函数的 `errors` 对象渲染错误。
- 用普通组件 + 手动错误显示替换 shadcn 表单组件,例如:`<Label htmlFor='name'>Name</Label>`、`<Input id='name' name='name' />`,然后 `{errors.name && <p className='text-sm text-destructive'>{errors.name}</p>}`。
- `<Select>` 必须添加 `name` 属性才能与 Inertia `<Form>` 集成,例如 `<Select name='role' defaultValue='member'>...</Select>`;shadcn 示例因为 react-hook-form 管理值而省略它。
- 初始化 shadcn:运行 `npx shadcn@latest init`。
- 如果 `tsconfig.json` 中没有 `@/` 解析别名,则添加它。
- 不要把 `@/` 解析别名添加到 `vite.config.ts`;`vite-plugin-ruby` 已经提供这些别名。
- Dialog 与 Inertia 导航集成:从 `@/components/ui/dialog` 导入 Dialog 相关组件,从 `@inertiajs/react` 导入 `router`;通过 `open` 和 `onOpenChange` 控制;当关闭时调用 `router.replaceProp('show_dialog', false)`。
- Dialog 的 `onOpenChange` 写为:`onOpenChange={(isOpen) => { if (!isOpen) { router.replaceProp('show_dialog', false) } }}`。
- Table 使用 shadcn `<Table>` 正常渲染;服务端排序通过 `router.get` 实现。
- 排序处理:`const handleSort = (column: string) => { router.get('/users', { sort: column }, { preserveState: true }) }`。
- 在 `TableHead` 上绑定点击排序:`onClick={() => handleSort('name')}`,加 `className='cursor-pointer'`,并根据 `sort` 显示箭头。
- 行链接使用 `<Link>`(不要用 `<a>`)以保留 SPA 导航。
- Toast 与 Flash 消息:flash 配置在 `inertia-rails-controllers`,flash 访问在 `inertia-rails-pages`;本技能只覆盖 toast UI 接线。
- 用 Sonner 实现基于 flash 的 toast 时,必须完整阅读 `references/flash-toast.md`(约 80 行),其中包含完整 `useFlash` hook 和 Sonner toast provider;如果只读取 flash 值而没有 toast UI,不要加载它。
- 关键坑:Rails initializer 中的 `flash_keys` 必须与 `FlashData` TypeScript 类型匹配;除非同时更新两处,否则不要使用 `success`/`error`。
- 暗色模式不使用 `next-themes`;`npx shadcn@latest init` 会生成 light/dark 的 CSS 变量,以及 Tailwind v4 的 `@custom-variant dark (&:is(.dark *));`,变量本身无需额外设置。
- 防止错误主题闪烁(FOUC):Next.js 自动处理,Inertia 不会;在 `<head>` 中、样式表之前添加内联脚本,在 React hydrate 之前切换 `dark` class。
- 内联脚本示例:在 `app/views/layouts/application.html.erb` 的 `<head>`、任何样式表之前,使用 `document.documentElement.classList.toggle('dark', localStorage.appearance === 'dark' || (!('appearance' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches))`。
- 在 Inertia 入口点调用 `initializeTheme()`:在 `app/frontend/entrypoints/inertia.tsx` 中 `import { initializeTheme } from '@/hooks/use-appearance'`,并在 `createInertiaApp` 之前调用 `initializeTheme()`。
- 使用 `useAppearance` hook(支持 light/dark/system 模式、localStorage 持久化、`matchMedia` 监听)替代 `next-themes`;通过 `<html>` 上的 `.dark` class 切换,无需 provider。
- 故障排除:`FormField`/`FormMessage` 崩溃时,说明使用了依赖 react-hook-form 的 shadcn 表单组件;改用普通 `Input`/`Label` + `errors.field` 显示。
- 故障排除:`Select` 值未提交时,缺少 `name` 属性;给 `<Select>` 添加 `name='field'`。
- 故障排除:Dialog 意外关闭时,缺少或错误的 `onOpenChange` 处理器;使用 `onOpenChange={(open) => { if (!open) closeHandler() }}`。
- 故障排除:主题闪烁时,缺少 `<head>` 内联脚本;在样式表之前添加暗色模式脚本。
- 构建超出上述范围的 shadcn 组件(Accordion、Sheet、Tabs、DropdownMenu、AlertDialog 的 Inertia 模式)时,加载 `references/components.md`(约 300 行)。
- 不要为基本 Form、Select、Dialog、Table 用法加载 `components.md`;上面的示例已足够。
- 需要完整 `<Form>` API 时参考 `inertia-rails-forms`;需要 flash 配置参考 `inertia-rails-controllers`;需要 flash 访问参考 `inertia-rails-pages`;需要 URL 驱动对话框参考 `inertia-rails-pages` 的 `router.get` 模式。
适用边界 · 注意事项
- 不适用于 Next.js 项目:该技能是 Inertia Rails React 适配,Next.js 应使用原生 shadcn/ui 与 Next.js 默认写法。
- 不适用于没有 Inertia.js、Rails、React 或 shadcn/ui 的项目。
- 如果决定使用 react-hook-form + zod 管理表单,不要按本技能替换为 Inertia <Form> 的方式,否则两套机制会冲突。