이벤트 처리
버튼 클릭, 입력 변경 등 다양한 DOM 이벤트를 Blazor에서 처리하는 방법을 익힙니다.
📌 학습 목표
- Blazor에서 DOM 이벤트를 C# 메서드에 연결하는 기본 원리를 이해합니다.
@onclick,@oninput,@onchange등 주요 이벤트 디렉티브를 사용할 수 있습니다.- 이벤트 인수(
EventArgs)를 받아 이벤트 상세 정보를 활용할 수 있습니다. - 람다 표현식으로 인라인 이벤트 핸들러를 작성할 수 있습니다.
- 이벤트 버블링과 기본 동작 방지 패턴을 적용할 수 있습니다.
📝 개념 설명
1. Blazor 이벤트 처리의 기본 원리
기존 JavaScript 개발에서는 addEventListener나 onclick 속성으로 DOM 이벤트를 처리했습니다. Blazor에서는 이 과정을 C# 메서드와 직접 연결할 수 있습니다. HTML 태그의 이벤트 속성 앞에 @ 기호를 붙이면 Blazor가 해당 이벤트를 가로채어 지정한 C# 핸들러 메서드를 호출합니다.
이벤트가 발생하면 Blazor는 자동으로 컴포넌트 상태를 갱신하고 렌더링을 재실행하므로, 개별 DOM 조작 코드 없이도 UI가 최신 상태를 반영합니다.
(클릭·입력 등)
이벤트 캐치
메서드 호출
UI 재렌더링
2. @onclick — 클릭 이벤트
@onclick은 가장 자주 사용하는 이벤트 디렉티브입니다. 버튼이나 다른 요소를 클릭했을 때 지정한 C# 메서드를 실행합니다.
<button @onclick="HandleClick">클릭하세요</button>
@code {
private void HandleClick()
{
// 클릭 시 실행할 코드
}
}
메서드 이름만 전달할 때는 괄호를 붙이지 않는다는 점에 주의하세요. @onclick="HandleClick()"이 아니라 @onclick="HandleClick"입니다.
3. MouseEventArgs — 클릭 상세 정보 받기
클릭한 위치, 어떤 마우스 버튼인지 등 이벤트 상세 정보가 필요하면 MouseEventArgs 매개변수를 핸들러에 추가합니다.
<button @onclick="HandleClickWithArgs">클릭 정보 확인</button>
@code {
private void HandleClickWithArgs(MouseEventArgs e)
{
Console.WriteLine($"클릭 위치: X={e.ClientX}, Y={e.ClientY}");
Console.WriteLine($"버튼 종류: {e.Button}"); // 0=왼쪽, 1=중간, 2=오른쪽
}
}
4. @oninput / @onchange — 입력 이벤트
텍스트 필드의 값이 변경될 때는 @oninput 또는 @onchange를 사용합니다.
- @oninput: 사용자가 키를 누를 때마다 실시간으로 발생합니다.
- @onchange: 입력 필드에서 포커스가 벗어날 때 발생합니다.
<input type="text" @oninput="OnInputChanged" />
<p>입력값: @inputValue</p>
@code {
private string inputValue = string.Empty;
private void OnInputChanged(ChangeEventArgs e)
{
inputValue = e.Value?.ToString() ?? string.Empty;
}
}
ChangeEventArgs의 Value 속성으로 현재 입력값을 가져옵니다.
5. 람다 표현식으로 인라인 핸들러 작성
간단한 로직이라면 별도 메서드를 만들지 않고 람다식으로 인라인 핸들러를 정의할 수 있습니다.
<button @onclick="() => count++">카운트 증가</button>
<p>현재 카운트: @count</p>
@code {
private int count = 0;
}
이벤트 인수도 함께 받으려면 다음처럼 작성합니다.
<button @onclick="(e) => HandleWithArgs(e, "추가 데이터")">버튼</button>
6. 비동기 이벤트 핸들러
이벤트 핸들러에서 API 호출이나 비동기 작업을 수행할 때는 async Task를 반환 타입으로 지정합니다. void 대신 Task를 반환해야 예외가 올바르게 처리됩니다.
<button @onclick="LoadDataAsync">데이터 불러오기</button>
@code {
private async Task LoadDataAsync()
{
await Task.Delay(1000); // 실제 코드에서는 API 호출
Console.WriteLine("데이터 로딩 완료");
}
}
7. 기타 주요 이벤트 디렉티브
Blazor는 표준 DOM 이벤트 대부분을 지원합니다.
8. 이벤트 버블링 방지와 기본 동작 방지
자식 요소의 이벤트가 부모 요소로 전파(버블링)되는 것을 막으려면 @onclick:stopPropagation을 사용합니다. 링크(<a>) 클릭 시 페이지 이동 같은 기본 동작을 막으려면 @onclick:preventDefault를 사용합니다.
<!-- 부모 클릭 이벤트로 전파 방지 -->
<div @onclick="ParentClick">
<button @onclick="ChildClick" @onclick:stopPropagation="true">
자식 버튼
</button>
</div>
<!-- 링크의 기본 이동 동작 방지 -->
<a href="https://example.com" @onclick="HandleLink" @onclick:preventDefault="true">
링크처럼 보이는 버튼
</a>
💡 예제 & 실습
실전 예제: 간단한 카운터 + 색상 변경 컴포넌트
지금까지 배운 여러 이벤트를 한 컴포넌트에서 사용하는 종합 예제입니다.
@page "/event-demo"
<h3>이벤트 처리 데모</h3>
<!-- 버튼 클릭 이벤트 -->
<button @onclick="Increment">+1</button>
<button @onclick="Decrement">-1</button>
<button @onclick="Reset">초기화</button>
<p>현재 카운트: <strong>@count</strong></p>
<hr />
<!-- 실시간 입력 이벤트 -->
<input type="text"
placeholder="이름을 입력하세요"
@oninput="OnNameChanged" />
<p>안녕하세요, <strong>@userName</strong>님!</p>
<hr />
<!-- 키보드 이벤트 -->
<input type="text"
placeholder="Enter 키를 눌러보세요"
@onkeydown="OnKeyDown" />
<p>마지막 키: @lastKey</p>
@code {
private int count = 0;
private string userName = "손님";
private string lastKey = "없음";
private void Increment() => count++;
private void Decrement() => count--;
private void Reset() => count = 0;
private void OnNameChanged(ChangeEventArgs e)
{
var value = e.Value?.ToString();
userName = string.IsNullOrEmpty(value) ? "손님" : value;
}
private void OnKeyDown(KeyboardEventArgs e)
{
lastKey = e.Key;
if (e.Key == "Enter")
{
Console.WriteLine("Enter 키 감지!");
}
}
}
단계별 해설
- Increment / Decrement / Reset: 람다 대신 짧은 메서드로 분리하여 가독성을 높였습니다.
=>표현식 바디로 한 줄로 작성합니다. - OnNameChanged:
ChangeEventArgs.Value는object?타입이므로ToString()과 null 처리가 필요합니다. - OnKeyDown:
KeyboardEventArgs.Key로 누른 키의 이름을 문자열로 받을 수 있습니다."Enter","Escape","ArrowUp"등 표준 키 이름을 사용합니다.
실전 예제 2: 마우스 위치 추적
<div @onmousemove="TrackMouse"
style="width:300px; height:200px; border:1px solid #ccc; background:#f5f5f5;">
마우스를 이 영역 위에서 움직여보세요
</div>
<p>마우스 위치: X=@mouseX, Y=@mouseY</p>
@code {
private double mouseX = 0;
private double mouseY = 0;
private void TrackMouse(MouseEventArgs e)
{
mouseX = Math.Round(e.OffsetX, 1);
mouseY = Math.Round(e.OffsetY, 1);
}
}
MouseEventArgs는 ClientX/Y(뷰포트 기준), OffsetX/Y(요소 기준), ScreenX/Y(화면 기준) 등 다양한 좌표 속성을 제공합니다.
⚠️ 자주 틀리는 것 / 주의사항
실수 1: 메서드 호출 괄호를 붙이는 경우
❌ @onclick="HandleClick()" — 컴파일 오류 또는 의도치 않은 동작
✅ @onclick="HandleClick" — 메서드 참조(괄호 없음)
단, 람다식으로 인자를 전달할 때는 괄호가 필요합니다: @onclick="() => HandleClick(param)"
실수 2: async void 사용
❌ private async void LoadAsync() — 예외가 조용히 삼켜짐
✅ private async Task LoadAsync() — 예외가 올바르게 전파됨
Blazor 이벤트 핸들러는 비동기일 때 반드시 async Task를 반환해야 합니다.
실수 3: @oninput과 @onchange 혼동
@oninput은 키를 누를 때마다 발생하고, @onchange는 포커스가 벗어날 때 발생합니다. 실시간 검색 같은 기능은 @oninput, 폼 저장 전 유효성 검사는 @onchange가 적합합니다.
실수 4: 루프 안에서 람다 변수 캡처
@foreach (var item in items)
{
<!-- ❌ 잘못된 예: item이 루프 마지막 값으로 고정됨 -->
<button @onclick="() => SelectItem(item)">@item.Name</button>
}
Blazor의 @foreach는 JavaScript와 달리 각 반복마다 새로운 변수 범위를 생성하므로 위 코드는 실제로 올바르게 동작합니다. 단, for (int i = 0; ...) 루프에서는 int captured = i;처럼 로컬 복사본이 필요합니다.
실수 5: StateHasChanged() 불필요하게 호출
표준 이벤트 핸들러(@onclick 등)가 종료되면 Blazor가 자동으로 렌더링을 재실행합니다. 핸들러 안에서 StateHasChanged()를 명시적으로 호출할 필요가 없습니다. 타이머 콜백이나 외부 이벤트처럼 Blazor 이벤트 사이클 밖에서 상태를 변경할 때만 사용하세요.
🎯 마무리
Blazor의 이벤트 처리는 HTML의 이벤트 속성 앞에 @만 붙이면 C# 메서드와 직접 연결됩니다. 이 단순한 원칙 위에 EventArgs 매개변수, 람다 인라인 핸들러, 비동기 Task 핸들러, 이벤트 수정자(stopPropagation·preventDefault)를 조합하면 실무에서 필요한 대부분의 사용자 상호작용을 처리할 수 있습니다.
이벤트 처리에 익숙해지면 컴포넌트가 ‘살아있는 UI’처럼 느껴지기 시작합니다. 다양한 이벤트 디렉티브를 직접 코드에 적용하며 손에 익히는 것이 가장 빠른 학습법입니다.
- Blazor 이벤트 디렉티브는 HTML 이벤트 속성 앞에
@를 붙인 형태입니다 (@onclick,@oninput등). - 메서드를 연결할 때는 괄호 없이 메서드 이름만 씁니다:
@onclick="HandleClick" - 이벤트 상세 정보는
MouseEventArgs,KeyboardEventArgs,ChangeEventArgs등으로 받습니다. - 간단한 로직은 람다식 인라인 핸들러로 처리할 수 있습니다:
@onclick="() => count++" - 비동기 핸들러는 반드시
async Task를 반환해야 합니다 (async void금지). - 이벤트 버블링 방지:
@onclick:stopPropagation, 기본 동작 방지:@onclick:preventDefault - 표준 이벤트 핸들러 종료 후 Blazor는 자동으로 UI를 재렌더링합니다.