Razor 컴포넌트 기초 완전 정복 — .razor 파일 문법과 생명주기

Razor 컴포넌트 기초
📚 시작하기 전에

Razor 컴포넌트 기초

.razor 파일의 기본 문법과 컴포넌트 생명주기를 이해합니다.

📌 학습 목표

  • .razor 파일의 세 가지 구성 요소(마크업·@code 블록·지시어)를 설명할 수 있습니다.
  • Razor 문법(@ 기호)을 사용해 C# 값 출력, 이벤트 연결, 조건/반복 렌더링을 구현할 수 있습니다.
  • 컴포넌트 매개변수([Parameter])를 선언하고 부모 컴포넌트에서 자식으로 값을 전달할 수 있습니다.
  • Blazor 컴포넌트 생명주기 메서드의 호출 순서를 이해하고 상황에 맞는 메서드를 선택할 수 있습니다.
  • 완전하고 실행 가능한 Razor 컴포넌트를 직접 작성할 수 있습니다.

📝 개념 설명

Razor 컴포넌트란?

Blazor 애플리케이션의 UI는 Razor 컴포넌트(Razor Component)라는 단위로 구성됩니다. 확장자가 .razor인 파일 하나가 곧 하나의 컴포넌트이며, HTML 마크업과 C# 코드를 한 파일 안에 작성할 수 있습니다.

 

React의 함수형 컴포넌트, Angular의 컴포넌트와 유사한 개념이지만, 별도의 템플릿 언어 없이 C#을 그대로 사용한다는 점이 핵심 차별점입니다.

 

 

📌 .razor 파일의 3대 구성 요소

 

🏗️
마크업 섹션
HTML + Razor 문법. @변수로 C# 값을 출력하고 @onclick 등으로 이벤트를 연결합니다.
⚙️
@code 블록
C# 필드·속성·메서드를 선언합니다. 이 블록은 컴포넌트 클래스의 일부로 컴파일됩니다.
📋
지시어(Directive)
@page로 라우팅 경로, @using으로 네임스페이스, @inject로 서비스 주입을 선언합니다.

.razor 파일 기본 구조

다음은 세 가지 구성 요소를 모두 담은 가장 기본적인 Razor 컴포넌트의 전체 구조입니다.

@* 1. 지시어 — 파일 최상단에 위치 *@
@page "/hello"
@using System.Collections.Generic

@* 2. 마크업 — HTML과 Razor 문법 혼합 *@
<h1>안녕하세요, @Name!</h1>
<p>현재 카운트: @count</p>
<button @onclick="Increment">클릭 (+1)</button>

@* 3. @code 블록 — C# 로직 *@
@code {
    [Parameter]
    public string Name { get; set; } = "World";

    private int count = 0;

    private void Increment()
    {
        count++;
    }
}

Razor 문법 핵심

① 값 출력 — @표현식

@변수명으로 C# 변수를 HTML에 직접 출력합니다. 복잡한 표현식은 괄호로 감쌉니다.

<p>이름: @userName</p>
<p>날짜: @DateTime.Now.ToString("yyyy-MM-dd")</p>
<p>계산 결과: @(price * quantity)원</p>

② 이벤트 핸들러 — @이벤트명

HTML 이벤트 이름 앞에 @를 붙여 C# 메서드를 연결합니다.

<button @onclick="HandleClick">클릭</button>
<input @oninput="HandleInput" placeholder="입력" />
<select @onchange="HandleChange"></select>

③ 조건부 렌더링 — @if

@if (isLoggedIn)
{
    <p>환영합니다!</p>
}
else
{
    <p>로그인이 필요합니다.</p>
}

④ 반복 렌더링 — @foreach

<ul>
    @foreach (var item in items)
    {
        <li>@item.Name — @item.Price원</li>
    }
</ul>

컴포넌트 매개변수 (Parameter)

부모 컴포넌트에서 자식 컴포넌트로 값을 전달할 때는 [Parameter] 특성(Attribute)을 사용합니다. 반드시 public 속성으로 선언해야 합니다.

자식 컴포넌트 — Alert.razor

<div class="alert alert-@Type">
    @Message
</div>

@code {
    [Parameter]
    public string Message { get; set; } = string.Empty;

    [Parameter]
    public string Type { get; set; } = "info";  // "info" | "warning" | "danger"
}

부모에서 사용

<Alert Message="저장이 완료되었습니다." Type="success" />
<Alert Message="오류가 발생했습니다." Type="danger" />

컴포넌트 생명주기 (Lifecycle)

Blazor 컴포넌트는 생성부터 소멸까지 정해진 순서로 생명주기 메서드가 호출됩니다. 각 메서드를 오버라이드(override)하여 원하는 시점에 코드를 실행합니다.

 

 

🔄 Blazor 컴포넌트 생명주기 흐름

 

① OnInitialized(Async)
컴포넌트 생성 시 최초 1회만 실행. 초기 데이터 로딩, 서비스 호출에 사용합니다.
② OnParametersSet(Async)
매개변수(Parameter)가 설정·변경될 때마다 실행. 매개변수 기반 계산이나 필터링에 사용합니다.
③ ShouldRender
렌더링 여부를 bool로 반환(기본값 true). 불필요한 렌더링을 막는 성능 최적화용입니다.
④ OnAfterRender(Async)
DOM에 반영된 직후 실행. JavaScript Interop 호출, DOM 요소 접근에 적합합니다.
⑤ Dispose
컴포넌트 제거 시 호출(IDisposable 구현 시). 타이머·이벤트 구독 등 리소스 정리에 사용합니다.

OnInitializedAsync — 초기 데이터 로딩

protected override async Task OnInitializedAsync()
{
    // 최초 1회만 실행
    products = await ProductService.GetAllAsync();
}

OnParametersSetAsync — 매개변수 변경 대응

protected override async Task OnParametersSetAsync()
{
    // 매개변수가 바뀔 때마다 실행
    filteredList = await DataService.GetByCategoryAsync(Category);
}

OnAfterRenderAsync — JavaScript 호출

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        // 무한 루프 방지를 위해 firstRender 조건 필수
        await JSRuntime.InvokeVoidAsync("initChart", "#myChart");
    }
}

IDisposable — 리소스 정리

@implements IDisposable

@code {
    private Timer? _timer;

    protected override void OnInitialized()
    {
        _timer = new Timer(Tick, null, 0, 1000);
    }

    private void Tick(object? state)
    {
        InvokeAsync(StateHasChanged);  // UI 갱신
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

💡 예제 & 실습 — 완전한 스마트 카운터

지금까지 배운 매개변수, 생명주기, 이벤트, Dispose를 모두 통합한 완전한 컴포넌트 예제입니다.

@* SmartCounter.razor *@
@page "/smart-counter"
@implements IDisposable

<h2>스마트 카운터</h2>
<p>현재 값: <strong>@count</strong> / 최대: @MaxCount</p>

<progress value="@count" max="@MaxCount" style="width:100%"></progress>

<div style="margin-top:8px">
    <button @onclick="Decrement" disabled="@(count <= MinCount)">−</button>
    <button @onclick="Increment" disabled="@(count >= MaxCount)">+</button>
    <button @onclick="Reset">초기화</button>
</div>

@if (count == MaxCount)
{
    <p style="color:green;font-weight:bold">✅ 최대값 도달!</p>
}
@if (count == MinCount)
{
    <p style="color:red">⚠️ 최솟값입니다.</p>
}

<p style="color:gray;font-size:0.85em">경과: @elapsed초</p>

@code {
    [Parameter] public int MaxCount     { get; set; } = 10;
    [Parameter] public int MinCount     { get; set; } = 0;
    [Parameter] public int InitialValue { get; set; } = 0;

    private int count;
    private int elapsed;
    private Timer? _timer;

    // ① 초기화 — 최초 1회
    protected override void OnInitialized()
    {
        count = InitialValue;
        _timer = new Timer(_ => InvokeAsync(() =>
        {
            elapsed++;
            StateHasChanged();
        }), null, 1000, 1000);
    }

    // ② 매개변수 변경 시 범위 보정
    protected override void OnParametersSet()
    {
        count = Math.Clamp(count, MinCount, MaxCount);
    }

    private void Increment() => count = Math.Min(count + 1, MaxCount);
    private void Decrement() => count = Math.Max(count - 1, MinCount);
    private void Reset()     => count = InitialValue;

    // ⑤ 소멸 시 타이머 정리
    public void Dispose() => _timer?.Dispose();
}

부모 페이지에서 사용

@page "/"

<SmartCounter MaxCount="5" MinCount="-5" InitialValue="0" />
<SmartCounter MaxCount="100" MinCount="0" InitialValue="50" />

단계별 해설

  1. @page “/smart-counter”: 이 컴포넌트를 /smart-counter URL로 직접 접근할 수 있게 합니다.
  2. @implements IDisposable: 타이머를 사용하므로 컴포넌트 제거 시 Dispose()로 정리합니다.
  3. [Parameter] 세 개: MaxCount, MinCount, InitialValue를 외부에서 주입하여 동작을 제어합니다.
  4. OnInitialized: 초기값 설정 + 1초마다 경과 시간을 업데이트하는 타이머를 시작합니다.
  5. InvokeAsync + StateHasChanged: 타이머는 백그라운드 스레드이므로 Blazor 동기화 컨텍스트로 전환 후 UI를 갱신합니다.
  6. OnParametersSet: 부모가 MaxCount/MinCount를 변경하면 현재 count를 유효 범위 안으로 자동 조정합니다.

⚠️ 자주 틀리는 것 / 주의사항

⚠️ [Parameter]는 반드시 public이어야 합니다
[Parameter]가 붙은 속성을 private으로 선언하면 부모에서 값을 전달할 수 없습니다. Blazor 5.0 이후 private [Parameter]에는 컴파일 경고가 발생합니다. 반드시 public get; set; 형태로 선언하세요.
⚠️ 외부 스레드에서 UI 갱신 시 InvokeAsync 필수
타이머 콜백, 소켓 이벤트 등 Blazor 동기화 컨텍스트 밖에서 UI를 변경할 때는 반드시 await InvokeAsync(StateHasChanged)를 사용해야 합니다. 직접 StateHasChanged()만 호출하면 스레드 안전성 문제가 발생할 수 있습니다.
⚠️ OnInitializedAsync vs OnParametersSetAsync 혼동 주의
OnInitializedAsync는 컴포넌트 생애에서 단 한 번만 실행됩니다. 부모가 매개변수를 변경할 때마다 데이터를 다시 로드해야 한다면 OnParametersSetAsync를 사용해야 합니다. 이 둘을 혼동하면 데이터가 갱신되지 않는 버그가 발생합니다.
⚠️ OnAfterRender 내 StateHasChanged 무한 루프 주의
OnAfterRender 안에서 조건 없이 StateHasChanged()를 호출하면 렌더 → OnAfterRender → StateHasChanged → 렌더 → … 무한 루프가 발생합니다. 반드시 if (firstRender) 같은 가드 조건을 걸어야 합니다.
⚠️ 파일명(PascalCase)이 곧 컴포넌트 태그명
SmartCounter.razor 파일은 자동으로 <SmartCounter /> 태그가 됩니다. Blazor는 태그명과 파일명의 대소문자를 완전히 일치시킵니다. 반드시 파스칼 케이스(PascalCase)를 사용하세요. 소문자로 시작하면 일반 HTML 요소로 인식될 수 있습니다.

🎯 마무리

Razor 컴포넌트는 Blazor 개발의 핵심 단위입니다. .razor 파일 하나가 하나의 독립적인 UI 조각이 되며, 마크업과 C# 코드를 한 파일에서 관리할 수 있습니다. 매개변수(Parameter)로 재사용 가능한 컴포넌트를 설계하고, 생명주기 메서드로 정확한 시점에 코드를 실행하는 패턴을 익히면 복잡한 UI도 체계적으로 구현할 수 있습니다.

 

실전에서 가장 중요한 판단은 어떤 생명주기 메서드를 언제 쓰느냐입니다. 초기 데이터 로드는 OnInitializedAsync, 매개변수 변경 대응은 OnParametersSetAsync, JavaScript 호출은 OnAfterRenderAsync, 리소스 정리는 Dispose라는 패턴을 체득하면 대부분의 상황에 대응할 수 있습니다.

✅ 핵심 정리
  • .razor 파일 = 마크업 + @code 블록 + 지시어(@page · @using · @inject)
  • @변수로 값 출력, @onclick으로 이벤트 연결, @if/@foreach로 조건·반복 렌더링
  • [Parameter] public T 속성명 { get; set; } — 부모→자식 값 전달의 기본 규칙
  • 생명주기 순서: OnInitialized → OnParametersSet → ShouldRender → OnAfterRender → Dispose
  • 외부 스레드에서 UI 갱신 시 await InvokeAsync(StateHasChanged) 반드시 사용
  • 파일명 = 컴포넌트 태그명 (반드시 PascalCase 준수)

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.