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

# ⚡Livewire 4 입문 — JavaScript 없이 리액티브한 UI 만들기

> Laravel의 퍼스트파티 패키지 Livewire 4를 사용하여 JavaScript를 쓰지 않고 인터랙티브한 UI를 구축하는 방법을, 설치부터 실전 코드 예시까지 설명합니다.

## Livewire란

Laravel에서 동적인 UI를 만들려고 하면, 이전에는 Vue.js나 React 등의 JavaScript 프레임워크를 조합하거나, 스스로 AJAX 요청을 작성할 수밖에 없었습니다.

**Livewire**는 그 문제를 해결하는 Laravel용 패키지입니다. PHP와 Blade만으로 리액티브한 UI를 구축할 수 있습니다. 폼의 실시간 유효성 검증, 라이브 검색, 카운터 등, 기존이라면 JavaScript가 필요했던 기능을 모두 PHP로 구현할 수 있습니다.

<Info>
  Livewire 4는 Laravel 10 이후, PHP 8.1 이후에서 동작합니다. 현재의 최신 버전은 Livewire 4입니다.
</Info>

### 구조 개요

Livewire 컴포넌트는 서버 상의 PHP 클래스와 Blade 템플릿의 조합입니다. 사용자가 버튼을 클릭하거나 폼에 입력하면, Livewire는 뒤에서 AJAX 요청을 보내 PHP를 실행하고, 변경된 부분만을 페이지에 반영합니다. 개발자는 이 구조를 의식하지 않고 PHP만으로 기술할 수 있습니다.

***

## 설치

Laravel 애플리케이션의 루트 디렉터리에서 다음 명령을 실행합니다.

```shell theme={null}
composer require livewire/livewire
```

Laravel의 패키지 자동 검출이 유효하므로 추가 설정은 불필요합니다.

### 레이아웃 파일 생성

풀 페이지 컴포넌트로 사용할 경우 레이아웃 파일이 필요합니다. 다음 Artisan 명령으로 생성할 수 있습니다.

```shell theme={null}
php artisan livewire:layout
```

`resources/views/layouts/app.blade.php`가 생성됩니다.

```blade theme={null}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <title>{{ $title ?? config('app.name') }}</title>

        @vite(['resources/css/app.css', 'resources/js/app.js'])

        @livewireStyles
    </head>
    <body>
        {{ $slot }}

        @livewireScripts
    </body>
</html>
```

`@livewireStyles`와 `@livewireScripts`가 Livewire와 Alpine.js의 애셋을 자동으로 로드합니다.

***

## 첫 컴포넌트

Livewire 컴포넌트를 생성하는 Artisan 명령이 준비되어 있습니다. 심플한 카운터를 만들어 봅시다.

```shell theme={null}
php artisan make:livewire Counter
```

이 명령으로 `resources/views/components/⚡counter.blade.php`라는 단일 파일 컴포넌트가 생성됩니다.

<Tip>
  파일명의 ⚡는 Livewire 컴포넌트를 한눈에 식별하기 위한 것입니다. 에디터에서의 시인성이 오릅니다. 불필요하면 설정으로 무효화할 수 있습니다.
</Tip>

생성된 파일을 다음과 같이 편집합니다.

```blade theme={null}
<?php

use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }

    public function decrement(): void
    {
        $this->count--;
    }
};
?>

<div>
    <h1>카운트: {{ $count }}</h1>

    <button wire:click="increment">+1</button>
    <button wire:click="decrement">-1</button>
</div>
```

Blade 템플릿에 이 컴포넌트를 삽입하려면 일반적인 Blade 컴포넌트 구문을 사용합니다.

```blade theme={null}
<livewire:counter />
```

버튼을 클릭할 때마다 페이지 리로드 없이 카운트가 갱신됩니다. `wire:click`이 JavaScript 대신 PHP 메서드를 호출하는 구조입니다.

***

## 프로퍼티와 액션

### 프로퍼티 — `wire:model`

`wire:model` 디렉티브로 입력 요소와 컴포넌트의 프로퍼티를 양방향 바인드할 수 있습니다.

```blade theme={null}
<?php

use Livewire\Component;

new class extends Component {
    public string $name = '';
    public string $email = '';
};
?>

<div>
    <input type="text" wire:model="name" placeholder="이름">
    <input type="email" wire:model="email" placeholder="이메일">

    <p>안녕하세요, {{ $name }}님 ({{ $email }})</p>
</div>
```

기본으로는 `wire:model`은 액션(폼 송신 등)이 실행되었을 때만 서버와 동기화합니다. 입력할 때마다 동기화시키고 싶은 경우는 `.live` 수식자를 추가합니다.

| 표기법                    | 동작                           |
| ---------------------- | ---------------------------- |
| `wire:model`           | 액션 실행 시(폼 송신 등)에만 동기화 (기본)   |
| `wire:model.live`      | 입력할 때마다 요청 전송                |
| `wire:model.blur`      | 포커스를 잃었을 때 동기화 (요청은 발생하지 않음) |
| `wire:model.live.blur` | 포커스를 잃었을 때 요청 전송             |

### 액션 — `wire:click`, `wire:submit`

`wire:click`은 클릭 이벤트, `wire:submit`은 폼의 송신 이벤트에 메서드를 연결합니다.

```blade theme={null}
<?php

use Livewire\Component;
use App\Models\Task;

new class extends Component {
    public string $taskName = '';

    public function addTask(): void
    {
        Task::create(['name' => $this->taskName]);
        $this->taskName = '';
    }

    public function render()
    {
        return $this->view([
            'tasks' => Task::latest()->get(),
        ]);
    }
};
?>

<div>
    <form wire:submit="addTask">
        <input type="text" wire:model="taskName" placeholder="태스크명">
        <button type="submit">추가</button>
    </form>

    <ul>
        @foreach ($tasks as $task)
            <li>{{ $task->name }}</li>
        @endforeach
    </ul>
</div>
```

***

## 실시간 유효성 검증

Livewire 4에서는 `#[Validate]` 어트리뷰트로 프로퍼티에 유효성 검증 룰을 직접 정의할 수 있습니다.

```blade theme={null}
<?php

use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[Validate('required|min:3')]
    public string $title = '';

    #[Validate('required|min:10')]
    public string $content = '';

    public function save(): void
    {
        $this->validate();

        Post::create([
            'title' => $this->title,
            'content' => $this->content,
        ]);

        $this->reset(['title', 'content']);

        session()->flash('message', '게시물을 저장했습니다.');
    }
};
?>

<div>
    @if (session('message'))
        <div>{{ session('message') }}</div>
    @endif

    <form wire:submit="save">
        <div>
            <input type="text" wire:model.live.blur="title" placeholder="제목">
            @error('title') <span style="color: red;">{{ $message }}</span> @enderror
        </div>

        <div>
            <textarea wire:model.live.blur="content" placeholder="본문"></textarea>
            @error('content') <span style="color: red;">{{ $message }}</span> @enderror
        </div>

        <button type="submit">저장</button>
    </form>
</div>
```

`#[Validate]`를 붙인 프로퍼티는 갱신할 때마다 자동 유효성 검증이 실행됩니다. `wire:model.live.blur`와 조합함으로써, 포커스를 잃은 순간에 오류 메시지가 표시되는 실시간 유효성 검증 경험을 실현할 수 있습니다.

<Info>
  `$this->validate()`는 폼 송신 시 전 프로퍼티를 일괄 검증합니다. `#[Validate]`에 의한 자동 유효성 검증과 2단계로 사용하는 것이 권장 패턴입니다.
</Info>

***

## 라이프사이클 훅

Livewire 컴포넌트에는 몇 가지 라이프사이클 훅이 있습니다.

| 훅                             | 타이밍                        |
| ----------------------------- | -------------------------- |
| `mount()`                     | 컴포넌트가 처음 생성될 때 (한 번만)      |
| `boot()`                      | 매 요청의 시작 시 (초기 · 후속 요청 양쪽) |
| `updating($property, $value)` | 프로퍼티가 갱신되기 직전              |
| `updated($property)`          | 프로퍼티가 갱신된 직후               |
| `rendering()`                 | 뷰의 렌더링 전                   |
| `rendered()`                  | 뷰의 렌더링 후                   |
| `dehydrate()`                 | 매 요청의 종료 시                 |

### `mount()` — 초기화

`mount()`는 컴포넌트의 초기화에 사용합니다. 생성자의 대신입니다.

```blade theme={null}
<?php

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

new class extends Component {
    public string $name = '';
    public string $email = '';

    public function mount(): void
    {
        $this->name = Auth::user()->name;
        $this->email = Auth::user()->email;
    }
};
?>

<div>
    <p>이름: {{ $name }}</p>
    <p>이메일: {{ $email }}</p>
</div>
```

### `updated()` — 프로퍼티 변경 후 처리

프로퍼티가 갱신된 후에 처리를 끼워 넣고 싶은 경우는 `updated()`를 사용합니다. 특정 프로퍼티로 좁히는 경우는 메서드 이름에 포함시킵니다.

```blade theme={null}
<?php

use Livewire\Component;

new class extends Component {
    public string $username = '';

    public function updatedUsername(): void
    {
        $this->username = strtolower($this->username);
    }
};
?>

<div>
    <input type="text" wire:model.live="username">
    <p>사용자명: {{ $username }}</p>
</div>
```

입력할 때마다 자동으로 소문자로 변환됩니다.

***

## Laravel과의 통합

### Eloquent 모델 활용

Livewire는 프로퍼티에 직접 Eloquent 모델을 유지할 수 있습니다.

```blade theme={null}
<?php

use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\User;

new class extends Component {
    public User $user;

    public function mount(User $user): void
    {
        $this->user = $user;
    }

    #[Validate('required|min:2')]
    public string $name = '';

    public function save(): void
    {
        $this->validate();
        $this->user->update(['name' => $this->name]);
        session()->flash('message', '프로필을 갱신했습니다.');
    }

    public function render()
    {
        return $this->view();
    }
};
?>

<div>
    @if (session('message'))
        <div>{{ session('message') }}</div>
    @endif

    <form wire:submit="save">
        <input type="text" wire:model="name" placeholder="이름">
        @error('name') <span style="color: red;">{{ $message }}</span> @enderror
        <button type="submit">갱신</button>
    </form>
</div>
```

### 폼 오브젝트

복잡한 폼은 폼 오브젝트로 분리함으로써 컴포넌트를 심플하게 유지할 수 있습니다.

```shell theme={null}
php artisan livewire:form PostForm
```

```php theme={null}
namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;
use App\Models\Post;

class PostForm extends Form
{
    #[Validate('required|min:3')]
    public string $title = '';

    #[Validate('required|min:10')]
    public string $content = '';

    public function store(): void
    {
        Post::create($this->only(['title', 'content']));
    }
}
```

폼 오브젝트를 컴포넌트에서 사용합니다.

```blade theme={null}
<?php

use App\Livewire\Forms\PostForm;
use Livewire\Component;

new class extends Component {
    public PostForm $form;

    public function save(): void
    {
        $this->form->validate();
        $this->form->store();
        $this->form->reset();
        session()->flash('message', '게시물을 작성했습니다.');
    }
};
?>

<div>
    <form wire:submit="save">
        <input type="text" wire:model="form.title" placeholder="제목">
        @error('form.title') <span style="color: red;">{{ $message }}</span> @enderror

        <textarea wire:model="form.content" placeholder="본문"></textarea>
        @error('form.content') <span style="color: red;">{{ $message }}</span> @enderror

        <button type="submit">게시</button>
    </form>
</div>
```

***

## 정리

Livewire가 특히 적합한 유스케이스를 정리합니다.

| 유스케이스   | Livewire로 실현할 수 있는 것       |
| ------- | -------------------------- |
| 폼 처리    | 실시간 유효성 검증, 오류 표시          |
| 데이터 일람  | 라이브 검색, 정렬, 페이지네이션         |
| 카운터나 토글 | 페이지 리로드 없는 UI 갱신           |
| 관리 화면   | Eloquent와의 직접 연동으로 CRUD 조작 |
| 위저드형 폼  | 스텝 관리를 PHP로 구현             |

Livewire는 "Blade를 사용해 본 적이 있다"라는 Laravel 엔지니어라면 오늘부터 사용을 시작할 수 있습니다. JavaScript 프레임워크의 학습 비용 없이 인터랙티브한 UI를 실현할 수 있는 것이 최대의 강점입니다.

SPA 급의 고도의 인터랙션이 필요한 장면에서는 Inertia.js가 적합하지만, 폼이나 관리 화면, 데이터 테이블 같은 용도라면 Livewire 쪽이 더 심플하게 구현할 수 있는 경우가 많습니다. 우선 작은 컴포넌트 하나부터 시험해 보세요.

<Card title="Livewire 공식 문서" icon="book-open" href="https://livewire.laravel.com/docs">
  Livewire의 전체 기능(파일 업로드, 페이지네이션, 테스트 등)에 대해서는 공식 문서를 참고하세요.
</Card>


## Related topics

- [Svelte 입문 — Inertia × Laravel에서 사용하는 기초 지식](/ko/blog/svelte-introduction.md)
- [Laravel을 시작하기 전에 필요한 지식](/ko/true-tutorial.md)
- [인증 입문](/ko/authentication.md)
- [Vue.js 입문 — Inertia × Laravel에서 사용하는 기초 지식](/ko/blog/vue-introduction.md)
- [Laravel에서 MCP 서버 만들기](/ko/advanced/mcp-server.md)
