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

# React 入門 — 搭配 Inertia × Laravel 使用的基礎知識

> Laravel + Inertia.js 使用 React 的入門指南。介紹自 laravel/ui 到目前啟動套件的歷史脈絡，並實務性地解說 useForm、usePage 等 Inertia React hook。

## 什麼是 React

React 是 Meta（原 Facebook）開發並維護的使用者介面建構用 JavaScript 函式庫。特色是宣告式 UI 描述與**元件式**架構，從小型 widget 到完整 SPA 都能廣泛應用。

React 的核心是透過**虛擬 DOM** 有效率地重新繪製。當狀態（state）改變時，React 只把差異反映到 DOM，因此不必手動操作 DOM。

<Info>
  本頁介紹的是 React 19 與 Inertia v3 的組合。Laravel 13 啟動套件預設使用此組合。
</Info>

### JSX 與 TSX

React 元件以 **JSX**（JavaScript XML）語法撰寫，可在 JavaScript 內直接使用類似 HTML 的寫法。

```jsx theme={null}
// JSX 範例
function Greeting({ name }) {
    return <h1>你好，{name}</h1>
}
```

啟動套件標準採用 **TypeScript**（`.tsx`）。型別定義能加強 IDE 補完，並可及早發現 bug。

```tsx theme={null}
// TSX（TypeScript）範例
type Props = {
    name: string
}

function Greeting({ name }: Props) {
    return <h1>你好，{name}</h1>
}
```

<Tip>
  Laravel React 啟動套件標準為 TypeScript + TSX。本頁範例全用 TSX 撰寫。
</Tip>

***

## 在 Laravel 中的定位

### 歷史

React 與 Laravel 的關係比 Vue 略淺，但目前已達到同等甚至更高的地位。

```mermaid theme={null}
timeline
    title Laravel 與 React 的歷程
    2019 : Laravel 6 — laravel/ui 加入 React scaffold
    2021 : Laravel 8 — Breeze 加入 Inertia React 堆疊
    2022 : Laravel 9 — 轉為 Vite
    2025 : Laravel 12 — 全面翻新啟動套件（React / Vue）
    2026 : Laravel 13 — 支援 Inertia v3 的啟動套件
```

**Laravel 6（2019 年）** 將認證 scaffold 拆到 `laravel/ui` 套件，同時提供 Vue 與 React 版 scaffold。當時 Vue 為主流，React 版存在感較弱。

**Laravel Breeze（2021 年）** 加入 Inertia + React 堆疊後才正式獲得採用，**Laravel 12（2025 年）** 啟動套件全面翻新，React 與 Vue 已完全並列（甚至更早顯示）。

### 目前主流風格：Inertia × React

目前 Laravel 中 React 使用方式的核心是 **Inertia × React**。Inertia 實現「Modern Monolith」架構，不用設計 API 即可從 Laravel Controller 直接把資料傳給 React 元件。

```mermaid theme={null}
graph LR
    Browser["瀏覽器"]
    Inertia["Inertia.js<br>（adapter 層）"]
    Laravel["Laravel<br>（Controller）"]
    React["React<br>（頁面元件）"]

    Browser <-->|XHR / 全頁載入| Inertia
    Inertia <-->|Inertia response| Laravel
    Inertia -->|props| React
    React -->|渲染| Browser
```

***

## 建置

### 透過啟動套件（推薦）

新專案最省事的是使用啟動套件。

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

於互動式 prompt 選擇 **React**，以下項目會全部自動設定：

* `inertiajs/inertia-laravel`（伺服器端 adapter）
* `@inertiajs/react`（用戶端 adapter）
* `react` + `react-dom`（React 19 本體）
* `@vitejs/plugin-react`（Vite plugin）
* TypeScript + `@types/react`
* Tailwind CSS + shadcn/ui 元件庫
* `HandleInertiaRequests` middleware
* 登入、註冊等認證畫面（以 Inertia + React + TypeScript 實作）

### 手動安裝

要加到既有專案，分別安裝伺服器端與用戶端。

```shell theme={null}
# 伺服器端（PHP）
composer require inertiajs/inertia-laravel

# 用戶端（JavaScript）
npm install @inertiajs/react react react-dom
npm install --save-dev @vitejs/plugin-react @types/react @types/react-dom typescript
```

接著在 `vite.config.ts` 加入 React plugin。

```ts theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import react from '@vitejs/plugin-react'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.tsx'],
            refresh: true,
        }),
        react(),
    ],
})
```

於 `resources/js/app.tsx` 啟動 Inertia 應用。

```tsx theme={null}
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.tsx`,
            import.meta.glob('./pages/**/*.tsx'),
        ),
    setup({ el, App, props }) {
        createRoot(el).render(<App {...props} />)
    },
})
```

<Info>
  手動安裝細節（根樣板設定、middleware 註冊等）請參考 [Inertia 官方文件](https://inertiajs.com/installation)。
</Info>

***

## 目錄結構

啟動套件會把 React 頁面元件放到 `resources/js/pages/`。

```
resources/js/
├── app.tsx            # Inertia 應用起點
├── bootstrap.ts
├── components/        # 可重用 UI 元件
│   ├── ui/            # shadcn/ui 元件
│   └── ...
├── hooks/             # 自訂 React hook
├── layouts/           # Layout 元件
│   ├── app-layout.tsx
│   └── auth-layout.tsx
├── lib/               # 工具函式、設定
├── pages/             # Inertia 頁面元件（對應 Controller 名稱）
│   ├── auth/
│   │   ├── login.tsx
│   │   └── register.tsx
│   ├── dashboard.tsx
│   └── posts/
│       ├── index.tsx
│       ├── create.tsx
│       └── show.tsx
└── types/             # TypeScript 型別定義
```

寫 `Inertia::render('posts/index', [...])` 時，`resources/js/pages/posts/index.tsx` 就是對應的元件。

***

## JSX 語法基礎

React 使用 JSX，在 JavaScript 中寫類 HTML 語法。為了讀懂啟動套件的程式碼，以下是必知的模式。

#### `{}` — 變數展開

JSX 中用 `{}` 嵌入 JavaScript 值或運算式。

```tsx theme={null}
const name = '世界'
const count = 3

return (
    <div>
        <p>你好，{name}！</p>
        <p>兩倍是 {count * 2}</p>
    </div>
)
```

#### 條件判斷 — `&&` 與三元運算子

React 沒有相當於 `v-if` 的指令。簡單條件用 `&&`，if/else 用三元運算子 `? :`。

```tsx theme={null}
{/* 條件 true 才顯示 */}
{isLoggedIn && <p>歡迎！</p>}

{/* if / else */}
{isLoggedIn ? <p>歡迎！</p> : <a href="/login">登入</a>}

{/* if / else if / else */}
{isLoggedIn
    ? <p>歡迎！</p>
    : role === 'admin'
        ? <p>以管理員登入</p>
        : <a href="/login">登入</a>
}
```

#### 列表渲染 — `.map()`

列表渲染用 `Array.map()`。為了差異更新有效率，`key` prop 必須指定。

```tsx theme={null}
<ul>
    {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
    ))}
</ul>
```

相當於 Vue 的 `v-for :key` 或 Svelte 的 `{#each}`。

#### `className` — CSS class 名稱

JSX 會被編譯為 JavaScript，`class` 為保留字，因此 CSS class 使用 `className`。

```tsx theme={null}
{/* HTML：<div class="container"> */}
<div className="container">...</div>
```

#### 事件 handler — 駝峰式

JSX 的事件屬性用駝峰式，並傳入函式參考。

```tsx theme={null}
<button onClick={handleClick}>點擊</button>

{/* 取消表單預設送出 */}
<form onSubmit={(e) => { e.preventDefault(); handleSubmit() }}>
    ...
</form>
```

***

## 頁面元件基礎

Inertia 頁面元件就是一般的 React 元件。由 Laravel Controller 傳來的資料以 props 接收。

### Controller

```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),
        ]);
    }
}
```

### React 頁面元件

```tsx theme={null}
// resources/js/pages/posts/index.tsx
import { Link } from '@inertiajs/react'

type Post = {
    id: number
    title: string
    created_at: string
}

type Props = {
    posts: {
        data: Post[]
        // paginate(10) 會傳回含分頁資訊的物件
        // current_page、last_page、per_page、total 等也可用
    }
}

export default function PostsIndex({ posts }: Props) {
    return (
        <div>
            <h1>貼文列表</h1>
            {posts.data.map((post) => (
                <article key={post.id}>
                    <h2>
                        <Link href={`/posts/${post.id}`}>{post.title}</Link>
                    </h2>
                    <p>{post.created_at}</p>
                </article>
            ))}
        </div>
    )
}
```

元件只要以參數接收 props 即可使用 Controller 傳來的資料。不需要定義 REST API。

***

## `Link` 元件

使用 `@inertiajs/react` 提供的 `<Link>` 元件時，頁面切換會以 XHR 進行，可避免瀏覽器全頁重新載入。

```tsx theme={null}
import { Link } from '@inertiajs/react'

export default function PostsIndex() {
    return (
        <div>
            {/* 基本連結 */}
            <Link href="/posts">貼文列表</Link>

            {/* 以 POST 方法送出的連結（例如刪除） */}
            <Link href="/posts/1" method="delete" as="button">
                刪除
            </Link>

            {/* preload（hover 時預先取得） */}
            <Link href="/posts/1" preload>
                查看貼文
            </Link>
        </div>
    )
}
```

寫法就像一般 `<a>` 標籤，但幕後 Inertia 只替換頁面元件，因而有 SPA 般的操作感。

***

## `Form` 元件

`@inertiajs/react` 提供的 `<Form>` 元件是啟動套件認證畫面採用的推薦表單樣式。以 props 指定 `action` 與 `method`，並以 children 函式（render prop）接收 `errors` 與 `processing`。

### 基本用法

```tsx theme={null}
import { Form } from '@inertiajs/react'

export default function PostCreate() {
    return (
        <Form action="/posts" method="post" className="flex flex-col gap-4">
            {({ errors, processing }) => (
                <>
                    <div>
                        <label htmlFor="title">標題</label>
                        <input id="title" name="title" type="text" required />
                        {errors.title && <p className="error">{errors.title}</p>}
                    </div>

                    <div>
                        <label htmlFor="content">內容</label>
                        <textarea id="content" name="content" />
                        {errors.content && <p className="error">{errors.content}</p>}
                    </div>

                    <button type="submit" disabled={processing}>
                        {processing ? '送出中...' : '發佈'}
                    </button>
                </>
            )}
        </Form>
    )
}
```

`<Form>` 的 children 是 `({ errors, processing }) => JSX` 的函式（render prop 模式）。`Form` 元件會自動算出並傳入這些值。表單欄位不使用 `onChange` handler，而是使用 HTML 原生的 `name` 屬性，交由瀏覽器標準的表單資料收集機制。

### 啟動套件的模式

啟動套件用 [Wayfinder](/zh-TW/blog/wayfinder-introduction) 將路由當作物件管理。`store.form()` 會回傳含有路由物件 `action` 與 `method` 的物件，用 spread 傳給 `<Form>`。

```tsx theme={null}
import { Form } from '@inertiajs/react'
import { store } from '@/routes/login'

export default function Login() {
    return (
        <Form
            {...store.form()}
            resetOnSuccess={['password']}
            className="flex flex-col gap-6"
        >
            {({ errors, processing }) => (
                <>
                    {/* 表單內容 */}
                </>
            )}
        </Form>
    )
}
```

指定於 `resetOnSuccess` 的欄位，會在送出成功時自動重置。可指定密碼等送出後想清空的欄位。

<Info>
  不使用 Wayfinder 時，直接傳 `action="/login"` 這樣的 URL 也能同樣運作。
</Info>

***

## `useForm` hook

表單處理使用 `@inertiajs/react` 的 `useForm` hook，能簡潔實作表單狀態管理、送出、驗證錯誤顯示。

### Controller 端

```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', '貼文已建立。');
    }
}
```

### React 表單元件

```tsx theme={null}
// resources/js/pages/posts/create.tsx
import { useForm } from '@inertiajs/react'
import { FormEventHandler } from 'react'

export default function PostCreate() {
    const { data, setData, post, processing, errors } = useForm({
        title: '',
        content: '',
    })

    const submit: FormEventHandler = (e) => {
        e.preventDefault()
        post('/posts')
    }

    return (
        <form onSubmit={submit}>
            <div>
                <label>標題</label>
                <input
                    type="text"
                    value={data.title}
                    onChange={(e) => setData('title', e.target.value)}
                />
                {errors.title && <p className="error">{errors.title}</p>}
            </div>

            <div>
                <label>內容</label>
                <textarea
                    value={data.content}
                    onChange={(e) => setData('content', e.target.value)}
                />
                {errors.content && <p className="error">{errors.content}</p>}
            </div>

            <button type="submit" disabled={processing}>
                {processing ? '送出中...' : '發佈'}
            </button>
        </form>
    )
}
```

`useForm` 回傳物件的主要屬性整理如下。

| 屬性 / 方法                 | 說明                   |
| ----------------------- | -------------------- |
| `data`                  | 表單資料物件               |
| `setData(field, value)` | 更新欄位值                |
| `errors`                | 驗證錯誤（以欄位名稱存取）        |
| `processing`            | 送出中為 `true`（可用於停用按鈕） |
| `isDirty`               | 已被修改過為 `true`        |
| `post(url)`             | 以 POST 送出            |
| `put(url)`              | 以 PUT 送出（更新）         |
| `delete(url)`           | 以 DELETE 送出          |
| `reset()`               | 重置為初始值               |

發生驗證錯誤時，`useForm` 會保留輸入內容並顯示錯誤。

***

## 共用資料（Shared Data）

所有頁面共通所需的資料（登入中使用者資訊、flash 訊息等），在 `HandleInertiaRequests` middleware 的 `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'),
            ],
        ]);
    }
}
```

React 元件從 `usePage()` hook 存取共用資料。

```tsx theme={null}
import { usePage } from '@inertiajs/react'

type SharedProps = {
    auth: {
        user: { id: number; name: string; email: string } | null
    }
    flash: {
        success: string | null
        error: string | null
    }
}

export default function AppHeader() {
    const { auth, flash } = usePage<SharedProps>().props

    return (
        <>
            <header>
                {auth.user ? (
                    <span>{auth.user.name}</span>
                ) : (
                    <span>訪客</span>
                )}
            </header>

            {flash.success && (
                <div className="alert-success">{flash.success}</div>
            )}
        </>
    )
}
```

<Info>
  共用資料會包含在每次請求中，建議只放最必要的內容。以 `fn()` 進行 lazy evaluation，就只有在真正被存取時才會被求值。
</Info>

***

## React hook 基礎

在 Inertia × React 開發中，該掌握的 React 基本 hook 如下。

### `useState` — 本地狀態管理

```tsx theme={null}
import { useState } from 'react'

export default function Counter() {
    const [count, setCount] = useState(0)
    const [isOpen, setIsOpen] = useState(false)

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setIsOpen(!isOpen)}>切換</button>
        </div>
    )
}
```

### `useEffect` — 副作用處理

```tsx theme={null}
import { useState, useEffect } from 'react'

export default function Timer() {
    const [seconds, setSeconds] = useState(0)

    useEffect(() => {
        const timer = setInterval(() => {
            setSeconds((s) => s + 1)
        }, 1000)

        // 清理函式
        return () => clearInterval(timer)
    }, []) // 空陣列 = 只在掛載時執行一次

    return <p>經過時間：{seconds} 秒</p>
}
```

### `useMemo` 與 `useCallback` — 效能最佳化

```tsx theme={null}
import { useMemo, useCallback } from 'react'
import { router } from '@inertiajs/react'

type Post = { id: number; title: string; published: boolean }
type Props = { posts: Post[] }

export default function PostsList({ posts }: Props) {
    // 值 memoize（posts 沒變就不重算）
    const publishedPosts = useMemo(
        () => posts.filter((post) => post.published),
        [posts],
    )

    // 函式 memoize（相依值沒變就不重建）
    const handleClick = useCallback((id: number) => {
        router.visit(`/posts/${id}`)
    }, [])

    return (
        <ul>
            {publishedPosts.map((post) => (
                <li key={post.id} onClick={() => handleClick(post.id)}>
                    {post.title}
                </li>
            ))}
        </ul>
    )
}
```

***

## TypeScript 支援

啟動套件的 React 版預設使用 TypeScript。與 Inertia 的型別定義結合，可確保 props 的型別安全。

### 全域型別定義

啟動套件在 `resources/js/types/index.d.ts` 定義共用資料型別。

```ts theme={null}
// resources/js/types/index.d.ts
export interface User {
    id: number
    name: string
    email: string
    email_verified_at?: string
}

export type PageProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
    auth: {
        user: User
    }
}
```

### 於頁面元件使用型別

```tsx theme={null}
import { PageProps } from '@/types'

type Post = {
    id: number
    title: string
    content: string
}

export default function PostsIndex({ auth, posts }: PageProps<{ posts: Post[] }>) {
    return (
        <div>
            <p>登入中：{auth.user.name}</p>
            {posts.map((post) => (
                <article key={post.id}>
                    <h2>{post.title}</h2>
                </article>
            ))}
        </div>
    )
}
```

***

## 總結

React 與 Laravel 搭配時，透過 Inertia 的「Modern Monolith」架構最能發揮實力。與 TypeScript 的相容性也極佳，適合大型應用開發。

| 元素                  | 角色                          |
| ------------------- | --------------------------- |
| Laravel Controller  | 路由、資料取得、驗證                  |
| `Inertia::render()` | 從 Controller 把資料傳給 React 元件 |
| React 頁面元件          | 接收 props 並渲染 UI             |
| `useForm`           | 表單狀態管理、送出、錯誤顯示              |
| `Link` 元件           | 無全頁載入的頁面切換                  |
| `usePage().props`   | 存取共用資料                      |
| TypeScript          | props 型別安全、加強 IDE 補完        |

Inertia × React 可獲得 Laravel 後端的簡潔與 React 強大生態系兼具的開發體驗。啟動套件建立專案後含認證畫面在內都能馬上開始開發。

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


## Related topics

- [Svelte 入門 — 搭配 Inertia × Laravel 使用的基礎知識](/zh-TW/blog/svelte-introduction.md)
- [Vue.js 入門 — 搭配 Inertia × Laravel 使用的基礎知識](/zh-TW/blog/vue-introduction.md)
- [認證入門](/zh-TW/authentication.md)
- [開始學習 Laravel 前需要具備的知識](/zh-TW/true-tutorial.md)
- [前端](/zh-TW/frontend.md)
