반복 렌더링과 리스트
@foreach, @for 지시어로 컬렉션 데이터를 반복해 리스트 UI를 구성합니다.
📌 학습 목표
이 글을 완독하면 다음을 할 수 있습니다:
- @foreach 지시어를 사용해 컬렉션 데이터를 반복 렌더링할 수 있습니다.
- @for 지시어로 인덱스 기반 반복 UI를 구성할 수 있습니다.
- List<T>, 배열 등 다양한 컬렉션 타입을 Blazor UI에 출력할 수 있습니다.
- @key 속성으로 리스트 렌더링 성능을 최적화할 수 있습니다.
- 빈 컬렉션을 안전하게 처리하고 조건부 메시지를 표시할 수 있습니다.
📝 개념 설명
1. Blazor의 반복 렌더링이란?
Blazor 컴포넌트에서는 C#의 반복문을 Razor 지시어 형태로 HTML 템플릿 안에 직접 작성할 수 있습니다. @foreach와 @for는 컬렉션을 순회하며 각 항목을 HTML 요소로 렌더링하는 핵심 메커니즘입니다. 일반 C# 코드와 달리, Razor 템플릿 안에서 반복문을 사용할 때는 @ 접두사를 붙여 Blazor 엔진이 C# 구문임을 인식하도록 해야 합니다.
2. @foreach 지시어
@foreach는 컬렉션(List, 배열, IEnumerable 등)의 각 요소를 순서대로 꺼내어 HTML 블록을 반복 생성합니다. 인덱스가 필요 없고 전체 항목을 순회할 때 가장 간결한 방법입니다.
@foreach (var 변수명 in 컬렉션)
{
<HTML요소>@변수명</HTML요소>
}
3. @for 지시어
@for는 인덱스를 직접 제어해야 할 때 사용합니다. 순번 출력, 홀수 번째 항목 강조 등 인덱스 값이 필요한 경우에 적합합니다.
@for (int i = 0; i < 컬렉션.Count; i++)
{
<HTML요소>@컬렉션[i]</HTML요소>
}
4. @key 속성 — 렌더링 성능 최적화
Blazor의 가상 DOM 비교(Diffing) 알고리즘은 리스트 항목이 추가·삭제·이동될 때 어떤 항목이 변경되었는지 추적합니다. @key 속성에 고유 식별자(ID 등)를 지정하면 변경 전후 항목을 정확히 매핑하여 불필요한 DOM 재생성을 방지합니다.
@foreach (var item in items)
{
<div @key='item.Id'>@item.Name</div>
}
- 항목을 동적으로 추가·삭제·재정렬하는 리스트
- 각 항목이 고유한 ID를 가진 모델 객체
- 리스트 항목 내에
<input>등 입력 필드가 포함된 경우
5. 빈 컬렉션 처리
컬렉션이 비어 있을 때 아무 내용도 렌더링되지 않으면 사용자 경험이 저하됩니다. @if와 @foreach를 조합하여 빈 상태 메시지를 표시하는 것이 좋은 관행입니다.
@if (items.Count == 0)
{
<p>표시할 항목이 없습니다.</p>
}
else
{
@foreach (var item in items)
{
<li>@item</li>
}
}
| 항목 | @foreach | @for |
|---|---|---|
| 사용 목적 | 전체 컬렉션 순회 | 인덱스 제어 필요 시 |
| 코드 가독성 | 높음 (간결) | 중간 (조건 표현 가능) |
| 인덱스 접근 | 별도 변수 필요 | 기본 제공 (i 변수) |
| 주요 용도 | 일반 리스트 출력 | 순번·홀짝·범위 처리 |
| 지원 타입 | IEnumerable 전체 | 인덱서 지원 컬렉션 |
💡 예제 & 실습
예제 1. @foreach로 문자열 리스트 출력
가장 기본적인 형태로, List<string>을 <ul> 목록으로 렌더링합니다.
@page "/fruits"
<h3>과일 목록</h3>
<ul>
@foreach (var fruit in fruits)
{
<li>@fruit</li>
}
</ul>
@code {
private List<string> fruits = new()
{
"사과", "바나나", "오렌지", "포도"
};
}
렌더링 결과: 사과 / 바나나 / 오렌지 / 포도 항목이 순서대로 <li>로 출력됩니다.
예제 2. @for로 순번과 함께 출력
인덱스 i를 활용해 순번을 함께 표시합니다.
@page "/ranking"
<h3>인기 프로그래밍 언어 순위</h3>
<ol>
@for (int i = 0; i < languages.Count; i++)
{
<li><strong>@(i + 1)위</strong> — @languages[i]</li>
}
</ol>
@code {
private List<string> languages = new()
{
"C#", "Python", "JavaScript", "Java"
};
}
@(i + 1)처럼 괄호로 감싸면 C# 표현식을 직접 계산해 출력할 수 있습니다. 0-based 인덱스를 1-based 순번으로 변환하는 일반적인 패턴입니다.
예제 3. 객체 리스트 + @key 성능 최적화
실무에서는 모델 클래스 컬렉션을 다루는 경우가 많습니다. @key와 빈 컬렉션 처리를 함께 적용합니다.
@page "/products"
<h3>상품 목록</h3>
@if (products.Count == 0)
{
<p>등록된 상품이 없습니다.</p>
}
else
{
<ul>
@foreach (var p in products)
{
<li @key='p.Id'>
<strong>@p.Name</strong> — ₩@p.Price.ToString("N0")
</li>
}
</ul>
}
@code {
private List<Product> products = new()
{
new Product { Id = 1, Name = "노트북", Price = 1200000 },
new Product { Id = 2, Name = "마우스", Price = 35000 },
new Product { Id = 3, Name = "키보드", Price = 89000 }
};
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
}
}
<li @key='p.Id'>— 고유 ID로 Blazor Diffing 최적화p.Price.ToString("N0")— 숫자를 천 단위 콤마 형식으로 출력Name { get; set; } = ""— null 방지 기본값 지정
예제 4. 중첩 @foreach — 카테고리별 리스트
@foreach 안에 또 다른 @foreach를 중첩하여 계층형 데이터를 표현할 수 있습니다.
@page "/menu"
@foreach (var category in menu)
{
<h4>@category.CategoryName</h4>
<ul>
@foreach (var item in category.Items)
{
<li>@item</li>
}
</ul>
}
@code {
record MenuCategory(string CategoryName, List<string> Items);
private List<MenuCategory> menu = new()
{
new("한식", new() { "비빔밥", "김치찌개", "불고기" }),
new("양식", new() { "파스타", "스테이크", "피자" })
};
}
⚠️ 자주 틀리는 것 / 주의사항
⚠️ 주의 1. @for 인덱스 범위 초과
i <= 컬렉션.Count처럼 <=을 사용하면 마지막 인덱스를 초과하여 IndexOutOfRangeException이 발생합니다. 반드시 <(엄격한 미만)을 사용하세요.
@* ❌ 잘못된 예 — 인덱스 초과 오류 *@
@for (int i = 0; i <= items.Count; i++) { ... }
@* ✅ 올바른 예 *@
@for (int i = 0; i < items.Count; i++) { ... }
⚠️ 주의 2. @key 없는 동적 리스트
@key를 지정하지 않으면 Blazor는 위치(순서)로만 항목을 구분합니다. 리스트 중간에서 항목을 삭제하면 이후 항목들의 DOM이 불필요하게 전부 다시 렌더링되어 성능이 저하되고, 입력 필드의 값이 잘못된 항목에 남을 수 있습니다.
@* ❌ 동적 리스트에 @key 없음 *@
@foreach (var item in items)
{
<div>@item.Name</div>
}
@* ✅ 고유 키 지정 *@
@foreach (var item in items)
{
<div @key='item.Id'>@item.Name</div>
}
⚠️ 주의 3. null 컬렉션 순회
컬렉션 자체가 null이면 @foreach에서 NullReferenceException이 발생합니다. 선언 시 빈 컬렉션으로 초기화하는 습관을 들이세요.
@* ❌ null이면 런타임 오류 *@
private List<string> items;
@* ✅ 빈 리스트로 초기화 *@
private List<string> items = new();
⚠️ 주의 4. 람다 이벤트에서 루프 변수 캡처
버튼 클릭 등 이벤트 핸들러 람다에서 루프 변수를 직접 참조할 때, 복잡한 비동기 시나리오에서 마지막 값만 캡처될 수 있습니다. 로컬 변수에 복사하는 패턴을 권장합니다.
@foreach (var item in items)
{
var current = item; // 안전한 로컬 복사
<button @onclick='() => HandleClick(current)'>@current</button>
}
🎯 마무리
Blazor의 반복 렌더링은 @foreach와 @for 두 지시어를 중심으로 동작합니다. 전체 컬렉션 순회에는 @foreach가 가장 간결하고, 인덱스가 필요한 경우에는 @for를 선택합니다. 동적으로 변경되는 리스트에는 반드시 @key를 지정하여 렌더링 성능을 보호하고, 컬렉션은 항상 new()로 초기화하여 null 오류를 원천 차단하세요.
- @foreach: 컬렉션 전체 순회.
var item in collection패턴으로 각 요소에 접근 - @for: 인덱스 기반 반복.
i변수로 순번 표시·범위 제어 가능 - @key: 고유 식별자 지정 → Blazor Diffing 최적화, DOM 재생성 최소화
- 빈 컬렉션:
@if (items.Count == 0)으로 빈 상태 메시지 별도 처리 - null 방지: 컬렉션은 선언 시
new()로 초기화 필수 - 중첩 반복:
@foreach안에@foreach중첩 가능 — 카테고리별 계층형 데이터 표현에 활용