🦮 Blazor 입문
지난 시간에는 Blazor의 Binding의 추가적인 내용에 대해서 학습하는 시간을 가졌다. 첫 번째 내용은 HTML의 속성에도 바인딩 할 수 있는 것과 두 번째 내용으로는 conditional atrribue 속성 자체에도 조건을 붙일 수 있다는 것이다. 이번 시간에는 Parameter, Ref, EventCallback 등 다양한 개념에 대해서 학습하는 시간을 가진다. 이번에도 직접 실습해보며 페이지를 제작해보자.
🏄♀️ Parameter, Ref, EventCallback
우리가 지금까지 페이지를 만들 때 보면 레이저 컴포넌트를 활용해 페이지를 만들 수 있었다. 그리고 이러한 컴포넌트라는 말에서 비롯하여 여러 스크립트를 부품화하여 조립해 페이지를 구성한 것을 알 수 있다. 따라서 우리가 작성한 user.razor 스크립트의 일부 내용을 다른 razor 컴포넌트로 빼 가지고 관리할 수 있다는 의미가 된다.
그렇다면 user.razor에 <ul>~</ul> 부분을 재활용한다고 하면 어떻게 해야할까? 가장 간단하게 드는 해결 방법으로는 해당 영역을 똑같이 복사-붙여넣기하여 처리하는 것이다. 하지만 이런식으로 처리하면 동일한 코드가 여러개가 되기 때문에 관리하기가 매우 힘들어진다. 따라서 오늘은 코드를 컴포넌트로 빼내어 관리하는 방법에 대해서 알아보도록 하자.
지금까지 작성한 Count.razor를 User.razor에 넣어서 실행시켜보자. 아래와 같이 코드를 수정해서 작성하면 다음과 같은 실행 결과를 볼 수 있다.
@page "/user"
@using BlazorApp.Data;
// Counter.razor 추가
<Counter></Counter>
<h3>Online Users</h3>
<br />
<!-- 유저 추가 -->
<div class="container">
...
</div>
@code
{
// 유저 데이터 생성
List<UserData> _users = new List<UserData>();
ShowUser _showUser;
...
}
User 페이지에 Counter 페이지에 있던 내용이 떡하니 박혀있는 것을 볼 수 있다. 따라서 다른 컴포넌트를 활용하는 방법은 매우 간단한 것을 알 수 있다.
이제 별도의 컴포넌트를 통해 처리하고 관리하는 방법에 대해서 알아보도록 하자. 새롭게 ShowUser.razor 파일을 만들어 주고, 기존 User.razor에 있던 내용을 복사 붙여넣기 하자. 이 후 User.razor에 해당 컴포넌트를 이전 Count와 마찬가지로 참조하기 위해 수정한다.
<p>
Users: <b>@_users.Count()</b>
</p>
<br />
<ul class="list-group">
<!-- 이름 출력 -->
@foreach (var user in _users)
{
<li @key="user" class="list-group-item">
<!-- 유저 삭제 -->
<button type="button" class="btn btn-link" @onclick="() => KickUser(user)">Kick</button>
<label>@user.Name</label>
</li>
}
</ul>
@code {
}
@page "/user"
@using BlazorApp.Data;
<h3>Online Users</h3>
// ShowUser.razor 추가 🫠
<ShowUser></ShowUser>
<br />
<!-- 유저 추가 -->
<div class="container">
...
</div>
@code
{
// 유저 데이터 생성
List<UserData> _users = new List<UserData>();
...
}
이와 같이 작성한다면 기존에 있던 User.razor에 있던 코드를 가지고 오기 때문에 당연히 문제가 생긴다. 왜냐하면 _users는 기존 User.razor에서 사용되었기 때문이다. 따라서 User.razor에서 코드를 모두 복사-붙여넣기 할 수 있겠지만 당연히 그렇게 처리하면 안된다. 데이터를 여러 군데에서 관리하는 문제점과 더불어 데이터 불일치가 나타날 수 있기 때문이다. 따라서 <ShowUser></ShowUser>에 '_users' 원본 데이터를 전송해야 한다.
따라서 이러한 문제를 해결하기 위해 나타난 기능이 바로 파라미터 라는 기능이다. 한마디로 ShowUser를 인자로 넘겨주는 방법이다. User.razor에서 사용하던 List<UserData> _users = new List<UserData>();를 ShowUser.raozr에 복사해주고, 해당 데이터를 사용한다고 명시하기 위해 @using BlazorApp.Data;를 통해 인젝션 주입을 처리한다.
이 후, 해당 변수 앞에 [Parameter]라는 힌트를 주고, 해당 변수를 { get; set; }을 통해 변경하여 처리할 수 있도록 바꿔준다. 마지막으로 User.razor에서는 _user를 넘겨주기 위해 <ShowUser Users="_users"></ShowUser>로 변경하여 처리한다.
@using BlazorApp.Data;
<p>
Users: <b>@_users.Count()</b>
</p>
<br />
<ul class="list-group">
<!-- 이름 출력 -->
@foreach (var user in _users)
{
<li @key="user" class="list-group-item">
<!-- 유저 삭제 -->
<button type="button" class="btn btn-link" @onclick="() => KickUser(user)">Kick</button>
<label>@user.Name</label>
</li>
}
</ul>
@code
{
[Parameter]
public List<UserData> Users { get; set; }
}
@page "/user"
@using BlazorApp.Data;
<h3>Online Users</h3>
<ShowUser Users="_users"></ShowUser>
<br />
...
이 후, 실행을 하면 데이터가 정상적으로 공유되어 처리가 되는 것을 볼 수 있다.
위 단계를 통해 레이저 컴포넌트 끼리 서로 데이터를 넘기는 것을 처리하였다. 다음은 ShowUser 내부에 있는 KickUser 함수에 대한 동작이다. ShowUser는 현재 KickUser 스크립트를 내부에서 사용할 수 없다. 따라서 두 번째 문제는 'ShowUser와 User가 부모 자식 관계로 서로 분리되어 있을 때 과연 서로의 함수를 호출할 수 있는가?'이다.
이를 해결하기 위해서는 다양한 방법이 존재하겠지만, 먼저 User.razor에서 KickUser() 스크립트를 복사하여 ShowUser에 붙여넣기 하는 방법이 있다. 이렇게 처리할 경우 KickUser는 정상적으로 호출 된다. 하지만 이렇게 될 경우 AddUser는 ShowUser에서 처리할 수가 없게된다. 따라서 AddUser 또한 복사하여 ShowUser에 붙여넣도록 하자.
@code
{
[Parameter]
public List<UserData> Users { get; set; }
public void AddUser(UserData user)
{
Users.Add(user);
}
public void KickUser(UserData user)
{
Users.Remove(user);
}
}
물론 모든 동작이 User 컴포넌트와 같을 순 없겠지만, 위와 같이 수정 후 User 컴포넌트를 수정하도록 하자. 이렇게 될 경우 ShouUser에서 동작하는 AddUser 함수를 User 컴포넌트에서 동작할 수 있도록 설정해야 하는데, 이 때 사용되는 것이 레퍼런스(ref)이다. 유저 컴포넌트를 아래와 같이 수정하도록 하자.
...
// @ref _showUser를 통해 ShowUser를 참조한다.
<ShowUser Users="_users" @ref="_showUser"></ShowUser>
<br />
<!-- 유저 추가 -->
...
@code
{
// 유저 데이터 생성
List<UserData> _users = new List<UserData>();
ShowUser _showUser;
// 유저 이름 입력
string _inputName;
void AddUser()
{
// _showUser.AddUser를 실행하도록 함수를 변경한다.
_showUser.AddUser(new UserData() { Name = _inputName });
_inputName = string.Empty;
}
}
이렇게 작성할 경우 부모가 갖고 있는 정보를 자식에게 넘겨주는 형태로 처리를 해보았다. 마지막으로 자식쪽에서 부모가 갖고 있는 함수를 호출하고 싶다고 가정하여 테스트해보자.
자식쪽에서 부모가 갖고 있는 함수를 호출하고 싶을 때에는 파라미터를 응용하면 된다. C# 같은 경우 delegate를 통해 Action, Event 사용할 수 있다. 따라서 Acion을 활용하여 CallbackTest 변수를 만들어주고 KickUser에서 이를 호출하도록 하자. 결국 ShowUser 함수에서는 어떤 함수가 동작할지는 모르지만, 부모에서 이 CallbackTest 연결을 잘 해놓았으면 해당 부분을 호출할 수 있게 된다.
반대로 유저 쪽에서는 파라미터를 통해 이를 처리한다. 파라미터에서 동작할 함수를 만들고 이를 실행해 주기만 하면 된다. 코드로는 다음과 같다.
@using BlazorApp.Data;
<p>
Users: <b>@Users.Count()</b>
</p>
<br />
<ul class="list-group">
<!-- 이름 출력 -->
@foreach (var user in Users)
{
<li @key="user" class="list-group-item">
<!-- 유저 삭제 -->
<button type="button" class="btn btn-link" @onclick="() => KickUser(user)">Kick</button>
<label>@user.Name</label>
</li>
}
</ul>
@code
{
[Parameter]
public List<UserData> Users { get; set; }
[Parameter]
public Action CallbackTest { get; set; }
public void AddUser(UserData user)
{
Users.Add(user);
}
public void KickUser(UserData user)
{
Users.Remove(user);
CallbackTest.Invoke();
}
}
...
// CallbackTest Action에 동작할 함수 정의
<ShowUser Users="_users" CallbackTest="CallbackTestFunc" @ref="_showUser"></ShowUser>
<br />
...
@code
{
...
void CallbackTestFunc()
{
_inputName = "Callback Test";
}
}
위와 같이 작성하면 KickUser를 ShowUser에서 불러 처리하면 User에 있는 CallbackTest가 CallbackTestFunc를 실행시키는 것을 볼 수 있다. 그러나 이 상태에서 실행해보면 유저를 Kick 했음에도 불구하고 UI가 갱신이 되지 않는데 이는 이전 시간과 마찬가지로 이벤트 방식으로 함수가 호출 되었을 떄에는 바인딩한 내용이 자동으로 갱신되지 않기 때문이다. 따라서 Action을 사용할 경우 StateHasChanged()함수를 사용하여야 한다.
그러나 Action 말고도 Callback을 처리할 수 있는데 바로 Blazor에서 만들어준 EventCallback을 사용하는 것이다. 이를 사용하면 Action과 다르게 StateHasChanged() 함수를 실행하지 않아도 자동으로 갱신이 된다.
[Parameter]
public /* Action */ EventCallback CallbackTest { get; set; }
public void KickUser(UserData user)
{
Users.Remove(user);
// Action - CallbackTest.Invoke();
CallbackTest.InvokeAsync(null);
}
void CallbackTestFunc()
{
_inputName = "Callback Test";
// Action - StateHasChanged();
// EventCallback - 코드 작성 필요 없음.
}
따라서 코드를 한 줄이라도 더 적게 타이핑할 수 있는 EventCallback을 권장한다. 😎👍
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 6-5-6. Blazor 입문 : Templated Component (0) | 2024.09.07 |
---|---|
Part 6-5-5. Blazor 입문 : Cascading Parameter (0) | 2024.09.06 |
Part 6-5-3. Blazor 입문 : Binding 보충 (0) | 2024.09.03 |
Part 6-5-2. Blazor 입문 : Binding 실습 (0) | 2024.09.02 |
Part 6-5-1. Blazor 입문 : Binding (1) | 2024.09.01 |