Blazor 부모-자식 컴포넌트 통신과 EventCallback

부모-자식 컴포넌트
📚 시작하기 전에

부모-자식 컴포넌트

EventCallback으로 부모-자식 컴포넌트 간 데이터를 양방향으로 전달하는 패턴을 익힙니다.

📌 학습 목표

  • Blazor 컴포넌트 트리 구조와 부모-자식 관계를 이해하고 설명할 수 있습니다.
  • [Parameter] 속성으로 부모 → 자식 방향의 데이터 전달을 구현할 수 있습니다.
  • EventCallback으로 자식 → 부모 방향의 이벤트 알림을 처리할 수 있습니다.
  • EventCallback<T>를 사용해 이벤트 발생 시 데이터를 함께 전달할 수 있습니다.
  • @bind-PropertyName 단축 문법으로 양방향 바인딩을 간결하게 구현할 수 있습니다.

📝 개념 설명

1. Blazor 컴포넌트 트리와 데이터 흐름

Blazor 앱은 컴포넌트 트리(Component Tree)로 구성됩니다. 최상위 App.razor를 시작으로 여러 컴포넌트가 부모-자식 관계를 형성하며 UI를 구성합니다. 이 트리 구조에서 데이터는 반드시 방향을 갖고 이동합니다.

  • 부모 → 자식: [Parameter] 속성으로 값을 ‘주입’합니다.
  • 자식 → 부모: EventCallback으로 이벤트를 ‘알립니다’.

이 두 방향을 함께 활용하면 완전한 양방향 데이터 흐름을 구현할 수 있습니다.

2. [Parameter] — 부모에서 자식으로 데이터 전달

자식 컴포넌트에 [Parameter] 속성을 선언하면, 부모 컴포넌트에서 태그 속성(Attribute) 형태로 값을 전달할 수 있습니다. 부모가 다시 렌더링될 때마다 자식의 파라미터 값도 자동으로 갱신됩니다.

@* ChildGreeting.razor (자식 컴포넌트) *@
<div>
    <p>안녕하세요, @Name 님!</p>
    <p>현재 점수: @Score점</p>
</div>

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

    [Parameter]
    public int Score { get; set; }
}
@* ParentPage.razor (부모 컴포넌트) *@
<ChildGreeting Name="홍길동" Score="@currentScore" />

@code {
    private int currentScore = 95;
}
💡 주의: 자식 컴포넌트 안에서 [Parameter] 값을 직접 수정하는 것은 권장하지 않습니다. 부모가 다음에 렌더링될 때 원래 값으로 덮어씌워지기 때문입니다.

3. EventCallback — 자식에서 부모로 이벤트 전달

EventCallback은 자식 컴포넌트가 부모에게 “무언가가 발생했다”고 알리는 메커니즘입니다. C#의 event와 유사하지만, Blazor 렌더링 사이클에 최적화되어 있습니다.

 

EventCallback의 핵심 특징:

  • 자식 컴포넌트에 [Parameter]로 선언합니다.
  • 부모 컴포넌트에서 핸들러 메서드를 연결합니다.
  • 자식이 InvokeAsync()를 호출하면 부모의 핸들러가 실행됩니다.
  • 이벤트 호출 후 부모의 StateHasChanged()자동으로 호출되어 UI가 갱신됩니다.
  • EventCallback이 null이어도 InvokeAsync()는 안전하게 동작합니다.
@* LikeButton.razor (자식 컴포넌트) *@
<button @onclick="HandleClick">❤️ 좋아요</button>

@code {
    [Parameter]
    public EventCallback OnLiked { get; set; }

    private async Task HandleClick()
    {
        await OnLiked.InvokeAsync();
    }
}
@* ArticlePage.razor (부모 컴포넌트) *@
<h3>좋아요 수: @likeCount</h3>
<LikeButton OnLiked="@AddLike" />

@code {
    private int likeCount = 0;

    private void AddLike()
    {
        likeCount++;
    }
}

4. EventCallback<T> — 데이터와 함께 이벤트 전달

단순한 알림이 아닌, 값을 함께 부모에 전달해야 할 때는 제네릭 버전인 EventCallback<T>를 사용합니다. InvokeAsync(value)에 전달할 데이터를 인수로 넘깁니다.

@* SearchBox.razor (자식 컴포넌트) *@
<input type="text" @bind="keyword" placeholder="검색어 입력" />
<button @onclick="Search">검색</button>

@code {
    private string keyword = string.Empty;

    [Parameter]
    public EventCallback<string> OnSearch { get; set; }

    private async Task Search()
    {
        await OnSearch.InvokeAsync(keyword);
    }
}
@* SearchPage.razor (부모 컴포넌트) *@
<SearchBox OnSearch="@HandleSearch" />

@if (!string.IsNullOrEmpty(result))
{
    <p>검색 결과: <strong>@result</strong></p>
}

@code {
    private string result = string.Empty;

    private void HandleSearch(string keyword)
    {
        result = $"'{keyword}'에 대한 결과를 표시합니다.";
    }
}
🔄 부모-자식 컴포넌트 통신 흐름
부모 컴포넌트
Parameter 값 설정
자식 컴포넌트
값 렌더링·처리
EventCallback
InvokeAsync() 호출
부모 핸들러 실행
StateHasChanged() 자동

5. @bind-PropertyName — 양방향 바인딩 단축 문법

자식에 X라는 [Parameter]XChanged라는 EventCallback<T>가 쌍으로 존재할 때, 부모에서 @bind-X 단축 문법을 사용할 수 있습니다.

@* 단축 문법 *@
<QuantitySelector @bind-Quantity="quantity" MaxQuantity="10" />

@* 위 코드는 아래와 완전히 동일합니다 *@
<QuantitySelector
    Quantity="@quantity"
    QuantityChanged="@((val) => quantity = val)"
    MaxQuantity="10" />
💡 네이밍 규칙: @bind-Quantity가 작동하려면 EventCallback의 이름이 반드시 QuantityChanged여야 합니다. 파라미터 이름 + “Changed”가 Blazor의 명명 규칙입니다.

💡 예제 & 실습 — 완성형 수량 선택기 컴포넌트

실무에서 자주 쓰이는 상품 수량 선택기를 통해 전체 통신 흐름을 단계별로 구현합니다.

Step 1: 자식 컴포넌트 (QuantitySelector.razor)

@* QuantitySelector.razor *@
<div class="qty-wrap">
    <button @onclick="Decrease" disabled="@(Quantity <= 1)">−</button>
    <span>@Quantity</span>
    <button @onclick="Increase" disabled="@(Quantity >= MaxQuantity)">+</button>
</div>

@code {
    [Parameter]
    public int Quantity { get; set; } = 1;

    [Parameter]
    public int MaxQuantity { get; set; } = 99;

    [Parameter]
    public EventCallback<int> QuantityChanged { get; set; }

    private async Task Increase()
    {
        if (Quantity < MaxQuantity)
            await QuantityChanged.InvokeAsync(Quantity + 1);
    }

    private async Task Decrease()
    {
        if (Quantity > 1)
            await QuantityChanged.InvokeAsync(Quantity - 1);
    }
}

Step 2: 부모 컴포넌트 (ShoppingCart.razor)

@* ShoppingCart.razor *@
<h3>장바구니</h3>
<p>상품명: Blazor 입문서</p>
<p>단가: 25,000원</p>

<label>수량:</label>
<QuantitySelector @bind-Quantity="quantity" MaxQuantity="10" />

<hr />
<p><strong>합계: @((quantity * 25000).ToString("N0"))원</strong></p>

@code {
    private int quantity = 1;
}

Step 3: 동작 흐름 해설

  1. 부모가 렌더링되며 Quantity=1, MaxQuantity=10을 자식에 전달합니다.
  2. 사용자가 + 버튼을 클릭하면 자식의 Increase()가 실행됩니다.
  3. QuantityChanged.InvokeAsync(Quantity + 1)으로 새 수량(2)을 부모에 알립니다.
  4. @bind-Quantity의 내부 핸들러가 quantity = 2로 갱신합니다.
  5. Blazor가 자동으로 UI를 다시 렌더링하여 합계가 50,000원으로 표시됩니다.
📌 EventCallback 핵심 포인트
⬇️
[Parameter]
부모 → 자식 단방향. 자식 컴포넌트가 속성으로 선언하면 부모가 값을 주입합니다.
⬆️
EventCallback
자식 → 부모 이벤트. InvokeAsync() 호출 시 부모 핸들러 실행 + UI 자동 갱신.
📦
EventCallback<T>
이벤트 + 데이터. InvokeAsync(값)으로 T 타입 데이터를 부모에 함께 전달.
🔗
@bind-X 문법
X Parameter + XChanged EventCallback<T> 쌍을 자동 연결하는 단축 표기.

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

① 자식이 [Parameter]를 직접 수정하면 안 됩니다

자식 내부에서 파라미터 값을 직접 변경하면, 부모가 다음번에 렌더링될 때 원래 값으로 덮어씌워집니다. 상태는 항상 부모가 소유하고, 자식은 변경 요청만 EventCallback으로 알려야 합니다.

// ❌ 잘못된 패턴 — 부모가 모르는 상태 변경
[Parameter] public int Count { get; set; }
private void Wrong() { Count++; }  // 다음 렌더링 때 원래 값으로 복원됨

// ✅ 올바른 패턴 — EventCallback으로 부모에 위임
[Parameter] public int Count { get; set; }
[Parameter] public EventCallback<int> CountChanged { get; set; }
private async Task Correct()
{
    await CountChanged.InvokeAsync(Count + 1);
}

② EventCallback은 async Task로 처리하세요

// ❌ await 없이 호출 — 컴파일러 경고 발생
private void HandleClick()
{
    OnClicked.InvokeAsync();  // Task를 버림
}

// ✅ await 적용
private async Task HandleClick()
{
    await OnClicked.InvokeAsync();
}

③ Action이나 Func<Task> 대신 EventCallback을 사용하세요

Action이나 Func<Task>를 콜백으로 쓸 수도 있지만, EventCallback이 권장됩니다:

  • EventCallback은 이벤트 후 부모의 StateHasChanged()자동으로 호출합니다.
  • Action은 자동 렌더링이 없어 UI 갱신이 누락될 수 있습니다.
  • EventCallbacknull이어도 안전하게 InvokeAsync()가 동작합니다.

④ @bind-X 명명 규칙을 지켜야 합니다

@bind-Value가 동작하려면 자식에 Value Parameter와 ValueChanged EventCallback이 정확히 이 이름으로 존재해야 합니다. 이름이 다르면 단축 문법은 컴파일 오류가 발생합니다.

🎯 마무리

[Parameter]EventCallback은 Blazor 컴포넌트 개발의 가장 기초가 되는 통신 패턴입니다. 부모가 상태를 소유하고 자식에게 값을 내려주며, 자식은 변화가 생기면 이벤트로 부모에 알리는 이 구조는 단방향 데이터 흐름(Unidirectional Data Flow)의 핵심 원리입니다.

실전 활용 팁:
  • 재사용 가능한 UI 컴포넌트(버튼, 입력 폼, 모달, 슬라이더 등)는 항상 이 패턴으로 설계하세요.
  • 자식이 복잡한 객체를 전달해야 한다면 EventCallback<MyClass>처럼 커스텀 클래스도 T로 지정할 수 있습니다.
  • 여러 단계를 건너 데이터를 전달해야 할 때는 CascadingValue / CascadingParameter나 별도의 상태 서비스를 고려하세요.
✅ 핵심 정리
  • [Parameter]: 부모 → 자식 단방향 데이터 전달. 자식 컴포넌트 속성에 선언하고 부모 태그에서 값을 설정합니다.
  • EventCallback: 자식 → 부모 이벤트 알림. InvokeAsync() 호출 시 부모 핸들러 실행 + StateHasChanged() 자동 호출.
  • EventCallback<T>: 이벤트 발생 시 T 타입 데이터를 부모에게 함께 전달하는 제네릭 버전.
  • @bind-X 단축 문법: 자식의 X Parameter + XChanged EventCallback<T> 쌍을 부모에서 간결하게 연결.
  • 자식은 상태를 직접 수정하지 않습니다. 변경 요청은 반드시 EventCallback으로 부모에 위임합니다.

댓글 남기기

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