조건부 렌더링
@if, @switch 지시어를 활용해 조건에 따라 UI를 동적으로 표시합니다.
📌 학습 목표
- Blazor Razor 구문에서 @if / @else if / @else 지시어로 조건별 UI를 분기할 수 있습니다.
- @switch / @case 지시어를 활용해 다중 조건을 간결하게 처리할 수 있습니다.
- 로그인 상태, 역할(Role), 데이터 존재 여부 등 실전 시나리오에 조건부 렌더링을 적용할 수 있습니다.
- 조건부 렌더링과 CSS 클래스 동적 바인딩을 조합하여 UI를 정교하게 제어할 수 있습니다.
- 불필요한 조건 중첩을 피하는 클린 코드 패턴을 작성할 수 있습니다.
📝 개념 설명
1. 조건부 렌더링이란?
조건부 렌더링(Conditional Rendering)은 특정 조건이 충족될 때만 HTML 요소를 화면에 표시하는 기법입니다. Blazor는 C# 코드와 HTML을 Razor 구문으로 혼합하기 때문에, C#의 if, switch 제어 흐름을 그대로 템플릿 안에 사용할 수 있습니다.
React의 {condition && <Component />}, Vue의 v-if와 유사한 개념이지만, Blazor에서는 순수 C# 문법을 @ 접두사만 붙여 사용합니다. 별도의 템플릿 문법을 새로 배울 필요가 없는 것이 Blazor의 큰 장점입니다.
2. @if 지시어 기본 문법
Blazor Razor 파일(.razor)에서 @if는 아래와 같이 사용합니다.
@* 단순 조건 *@
@if (조건식)
{
<p>조건이 참일 때 표시됩니다.</p>
}
@* else 포함 *@
@if (isLoggedIn)
{
<p>환영합니다, 사용자님!</p>
}
else
{
<p>로그인이 필요합니다.</p>
}
@* else if 다중 분기 *@
@if (score >= 90)
{
<span class='grade-a'>A 등급</span>
}
else if (score >= 80)
{
<span class='grade-b'>B 등급</span>
}
else if (score >= 70)
{
<span class='grade-c'>C 등급</span>
}
else
{
<span class='grade-f'>재수강 필요</span>
}
@code {
bool isLoggedIn = true;
int score = 85;
}
@if 블록 안에는 HTML 요소, 다른 컴포넌트, 심지어 또 다른 @if를 자유롭게 중첩할 수 있습니다. 중괄호 { } 안이 Razor 렌더링 영역이 됩니다.3. @switch 지시어
같은 변수에 대한 여러 값 비교는 @if / @else if를 반복하는 것보다 @switch가 훨씬 간결합니다. C#의 switch 문과 동일한 구조입니다.
@switch (userRole)
{
case "Admin":
<div class='admin-panel'>
<h4>관리자 대시보드</h4>
<button>사용자 관리</button>
</div>
break;
case "Editor":
<div class='editor-panel'>
<h4>편집자 메뉴</h4>
<button>글 작성</button>
</div>
break;
case "Viewer":
<p>읽기 전용 모드입니다.</p>
break;
default:
<p>알 수 없는 역할입니다.</p>
break;
}
@code {
string userRole = "Editor";
}
4. 조건부 렌더링과 CSS 클래스 동적 바인딩 조합
요소를 완전히 제거하는 것 외에도, CSS 클래스를 동적으로 변경하여 표시/숨김을 제어하는 패턴도 자주 사용됩니다.
@* 방법 1: @if로 요소 자체를 제거 (DOM에서 완전히 삭제) *@
@if (showAlert)
{
<div class='alert alert-danger'>오류가 발생했습니다!</div>
}
@* 방법 2: CSS class 동적 바인딩 (DOM에 존재하되 숨김) *@
<div class='@(showAlert ? "visible" : "hidden")'>
오류가 발생했습니다!
</div>
@* 방법 3: style 직접 제어 *@
<div style='display:@(showAlert ? "block" : "none")'>
오류가 발생했습니다!
</div>
@code {
bool showAlert = false;
}
@if)은 DOM에서 요소가 완전히 제거되므로 보안상 민감한 콘텐츠(관리자 버튼 등)에 적합합니다. 방법 2·3은 DOM에 요소가 남아있으므로 자주 토글되어 성능이 중요한 경우(애니메이션 등)에 적합합니다.💡 예제 & 실습
실습 1: 로그인 상태에 따른 네비게이션 분기
실무에서 가장 흔한 패턴입니다. 로그인 여부에 따라 완전히 다른 메뉴를 표시합니다.
@* NavMenu.razor (발췌) *@
<nav class='navbar'>
<a href='/'>홈</a>
@if (isAuthenticated)
{
<a href='/dashboard'>대시보드</a>
<a href='/profile'>내 프로필</a>
<button @onclick='Logout'>로그아웃</button>
}
else
{
<a href='/login'>로그인</a>
<a href='/register'>회원가입</a>
}
</nav>
@code {
private bool isAuthenticated = false;
private void Logout()
{
isAuthenticated = false;
}
}
해설: isAuthenticated 값이 false이면 로그인/회원가입 링크만, true이면 대시보드/프로필/로그아웃만 렌더링됩니다. 버튼 클릭 시 isAuthenticated가 변경되면 Blazor가 자동으로 UI를 다시 렌더링합니다.
실습 2: 데이터 로딩 상태 3단계 분기
API 호출 중 로딩 스피너 → 에러 메시지 → 실제 데이터 순서로 표시하는 전형적인 패턴입니다.
@* DataPage.razor *@
@page "/data"
@if (isLoading)
{
<div class='loading-spinner'>
<p>⏳ 데이터를 불러오는 중...</p>
</div>
}
else if (hasError)
{
<div class='error-box'>
<h4>❌ 오류 발생</h4>
<p>@errorMessage</p>
<button @onclick='RetryLoad'>다시 시도</button>
</div>
}
else if (items == null || items.Count == 0)
{
<p>📭 표시할 데이터가 없습니다.</p>
}
else
{
<ul>
@foreach (var item in items)
{
<li>@item.Name</li>
}
</ul>
}
@code {
private bool isLoading = true;
private bool hasError = false;
private string errorMessage = string.Empty;
private List<Item> items = new();
protected override async Task OnInitializedAsync()
{
try
{
items = await DataService.GetItemsAsync();
}
catch (Exception ex)
{
hasError = true;
errorMessage = ex.Message;
}
finally
{
isLoading = false;
}
}
private async Task RetryLoad()
{
isLoading = true;
hasError = false;
await OnInitializedAsync();
}
}
해설: 로딩·에러·빈 목록·정상 데이터를 각각 분기하면 사용자 경험이 크게 향상됩니다. finally 블록에서 isLoading = false를 설정하면 에러 발생 시에도 스피너가 반드시 해제됩니다.
실습 3: @switch로 주문 상태 배지 표시
@* OrderStatus.razor *@
<div class='order-card'>
<h4>주문 #@OrderId</h4>
@switch (Status)
{
case OrderStatus.Pending:
<span class='badge badge-yellow'>⏳ 결제 대기</span>
break;
case OrderStatus.Processing:
<span class='badge badge-blue'>🔄 처리 중</span>
break;
case OrderStatus.Shipped:
<span class='badge badge-purple'>🚚 배송 중</span>
break;
case OrderStatus.Delivered:
<span class='badge badge-green'>✅ 배송 완료</span>
break;
case OrderStatus.Cancelled:
<span class='badge badge-red'>❌ 취소됨</span>
break;
default:
<span class='badge badge-gray'>알 수 없음</span>
break;
}
</div>
@code {
[Parameter] public int OrderId { get; set; }
[Parameter] public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
Pending, Processing, Shipped, Delivered, Cancelled
}
해설: enum과 @switch의 조합은 상태 기반 UI의 정석 패턴입니다. string 비교보다 컴파일 타임에 오류를 잡을 수 있어 안전합니다.
| 항목 | @if / @else if | @switch / @case |
|---|---|---|
| 적합한 경우 | 범위 비교 (score >= 90), 복잡한 불린 조건 | 단일 변수의 특정 값 비교 (enum, string 상수) |
| 가독성 | 조건 2~3개까지는 간결함 | 조건 4개 이상이면 훨씬 깔끔 |
| 타입 지원 | 모든 조건식 | int, string, enum, char 등 상수 패턴 |
| fall-through | 없음 | break 필수 (C#은 암묵적 fall-through 불가) |
| default 처리 | else 블록 | default: 블록 |
⚠️ 자주 틀리는 것 / 주의사항
1. @if 블록에서 세미콜론 넣기
@* ❌ 잘못된 예 — Razor 지시어에 세미콜론 없음 *@
@if (isLoggedIn);
{
<p>환영합니다.</p>
}
@* ✅ 올바른 예 *@
@if (isLoggedIn)
{
<p>환영합니다.</p>
}
@if 뒤에 세미콜론을 붙이면 조건 블록이 빈 문으로 처리되어 항상 렌더링되거나 컴파일 오류가 납니다.
2. @switch의 break 누락
@* ❌ break 누락 — 컴파일 오류 *@
@switch (role)
{
case "Admin":
<p>관리자</p>
@* break 없음 → CS0163 오류 *@
case "User":
<p>일반 사용자</p>
break;
}
C#의 switch는 암묵적 fall-through를 허용하지 않으므로, 각 case 마지막에 반드시 break;를 작성해야 합니다.
3. null 체크 없이 객체 멤버 접근
@* ❌ user가 null이면 NullReferenceException *@
@if (user.IsAdmin)
{
<button>관리</button>
}
@* ✅ null 조건 연산자 활용 *@
@if (user?.IsAdmin == true)
{
<button>관리</button>
}
비동기 데이터 로딩 중에는 객체가 null일 수 있습니다. ?.(null 조건 연산자)를 사용하거나, 먼저 @if (user != null)으로 보호 조건을 설정하세요.
4. 조건부 렌더링과 컴포넌트 생명주기
@* 중요: @if로 컴포넌트를 제거하면 Dispose()가 호출됨 *@
@if (showComponent)
{
<MyHeavyComponent />
}
@* showComponent가 false가 되면 MyHeavyComponent는 완전히 소멸 *@
@* 다시 true가 되면 새 인스턴스가 생성되어 OnInitializedAsync()가 재실행됨 *@
@if로 컴포넌트를 숨기면 해당 컴포넌트의 상태(State)가 초기화됩니다. 상태를 유지하면서 숨기고 싶다면 CSS display:none 방식을 사용하세요.5. 중첩 @if의 가독성 저하
@* ❌ 과도한 중첩 — 읽기 어려움 *@
@if (isLoggedIn)
{
@if (isAdmin)
{
@if (hasPermission)
{
<button>삭제</button>
}
}
}
@* ✅ 조건 합치기 *@
@if (isLoggedIn && isAdmin && hasPermission)
{
<button>삭제</button>
}
🎯 마무리
Blazor의 조건부 렌더링은 C# 개발자에게 매우 친숙한 방식으로 동작합니다. @if와 @switch는 문법적으로 C#과 완전히 동일하며, HTML 블록을 C# 제어 흐름 안에 자연스럽게 포함시킬 수 있습니다.
실무에서는 로딩 상태 3단계 분기(로딩 중 → 에러 → 데이터), 인증·역할 기반 UI 분기, enum 기반 상태 배지 패턴이 가장 많이 사용됩니다. 이 세 가지 패턴을 직접 작성해보면 조건부 렌더링의 대부분 시나리오를 커버할 수 있습니다.
@if / @else if / @else: 범위 비교나 복합 조건에 사용. HTML 블록을 C# 조건식으로 감싸는 방식@switch / @case: 동일 변수의 다중 값 비교에 사용. enum·상수 비교에 특히 적합하며, 각 case에break;필수@if로 컴포넌트를 제거하면 DOM에서 완전히 삭제되고 상태가 초기화됨. 상태 유지가 필요하면 CSS 토글 사용- null 조건 연산자(
?.)나 null 보호@if로 비동기 데이터 로딩 중 NullReferenceException 방지 - 조건이 3개 이상 중첩되면
&&로 합치거나@switch로 전환하여 가독성 확보 - 보안 민감 콘텐츠는
@if로 DOM에서 완전 제거. 자주 토글되는 요소는 CSS 방식으로 성능 최적화