Blazor 구성 요소 수명 주기: 전체 가이드
저는 한동안 Blazor를 사용해 왔는데 솔직히 처음에는 수명 주기 방법이 저를 혼란스럽게 했습니다. OnInitialized 대 OnInitializedAsync? OnParametersSet 대 OnAfterRender? StateHasChanged는 실제로 언제 다시 렌더링을 실행합니까? 수많은 시행착오 끝에 마침내 나는 이 모든 것에 대한 견고한 정신 모델을 갖게 되었습니다.
라이프사이클을 한눈에
Blazor 구성 요소가 렌더링되면 다음 메서드를 순서대로 진행합니다.
SetParametersAsyncOnInitialized/OnInitializedAsyncOnParametersSet/OnParametersSetAsyncOnAfterRender/OnAfterRenderAsyncDispose/DisposeAsync
각각을 살펴보겠습니다.
SetParametersAsync
이것이 가장 먼저 호출되는 메소드입니다. 상위 구성 요소로부터 원시 ParameterView를 받습니다. 대부분의 경우 이를 재정의하지 않습니다. Blazor는 매핑 매개 변수를 자동으로 처리합니다. 하지만 할당하기 전에 맞춤 매개변수 처리 또는 유효성 검사가 필요한 경우:
public override async Task SetParametersAsync(ParameterView parameters)
{
// Custom logic before parameters are set
if (parameters.TryGetValue<string>("Title", out var title))
{
Console.WriteLine($"Title is being set to: {title}");
}
await base.SetParametersAsync(parameters);
}
나는 이것을 실제 프로젝트에서 정확히 한 번 사용했습니다. 대부분의 경우 건너뛰게 됩니다.
OnInitialized / OnInitializedAsync
여기에서 데이터 로드, 서비스 초기화, 기본값 설정 등의 설정 작업을 수행할 수 있습니다. 구성요소가 처음 생성될 때 한 번 실행됩니다.
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
}
}
나를 당황하게 만든 한 가지: Blazor Server에서는 사전 렌더링 중에 OnInitializedAsync가 두 번 호출됩니다. 서버 측 사전 렌더링 중 처음이고 SignalR 연결이 설정될 때 두 번째입니다. API 호출 비용이 많이 드는 경우 이를 처리하는 것이 좋습니다.
private bool isPrerendering = true;
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
isPrerendering = false;
}
또는 이중 호출을 완전히 피하려면 PersistentComponentState를 사용하는 것이 더 좋습니다.
OnParametersSet / OnParametersSetAsync
이는 상위 구성요소가 다시 렌더링되고 새 매개변수 값을 전달할 때마다 실행됩니다. 또한 OnInitialized 이후에도 실행됩니다. 매개변수 변경에 반응하는 적절한 위치는 다음과 같습니다.
[Parameter]
public int CategoryId { get; set; }
private int previousCategoryId;
private List<Product> products = new();
protected override async Task OnParametersSetAsync()
{
if (CategoryId != previousCategoryId)
{
previousCategoryId = CategoryId;
products = await Http.GetFromJsonAsync<List<Product>>(
$"api/products?category={CategoryId}");
}
}
CategoryId != previousCategoryId에 대한 확인이 중요합니다. 이것이 없으면 카테고리가 변경되지 않았더라도 상위 항목이 다시 렌더링될 때마다 데이터를 다시 로드하게 됩니다.
OnAfterRender / OnAfterRenderAsync
이는 구성 요소가 DOM에 렌더링된 후에 실행됩니다. firstRender 매개변수는 초기 렌더링인지 여부를 알려줍니다. 이 시점에 DOM 요소가 존재하므로 JS Interop 호출을 위한 위치는 다음과 같습니다.
[Inject]
private IJSRuntime JS { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initializeChart", chartElement);
}
}
나는 다시 렌더링할 때마다 JavaScript 라이브러리를 다시 초기화하는 것을 피하기 위해 firstRender를 많이 사용합니다. 이벤트 리스너, 차트 라이브러리 또는 DOM에 직접 닿는 모든 것을 설정하는 경우 여기로 이동합니다.
#렌더링해야함
이것은 덜 알려져 있지만 매우 유용합니다. StateHasChanged가 호출될 때 구성 요소를 다시 렌더링할지 여부를 제어합니다.
private bool shouldRender = true;
protected override bool ShouldRender() => shouldRender;
private void HeavyOperation()
{
shouldRender = false;
// Do a bunch of state changes without triggering renders
for (int i = 0; i < 1000; i++)
{
items[i].Process();
}
shouldRender = true;
StateHasChanged(); // Now render once with all changes
}
나는 이것을 큰 목록을 처리하는 구성 요소를 최적화하는 데 사용했습니다. 항목이 변경될 때마다 다시 렌더링하는 대신 업데이트를 일괄 처리하고 마지막에 한 번 렌더링합니다.
폐기 / DisposeAsync
UI에서 구성요소가 제거되면 모든 리소스를 정리해야 합니다. IDisposable 또는 IAsyncDisposable 구현:
@implements IAsyncDisposable
@code {
private Timer? timer;
private IJSObjectReference? jsModule;
protected override void OnInitialized()
{
timer = new Timer(OnTick, null, 0, 1000);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./timer.js");
}
}
public async ValueTask DisposeAsync()
{
timer?.Dispose();
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
일반적으로 폐기해야 할 항목: 타이머, 이벤트 구독, JS 모듈 참조, CancellationTokenSource 및 생성한 모든 IDisposable 서비스.
StateHasChanged이는 수명주기 방법은 아니지만 밀접하게 관련되어 있습니다. Blazor에게 “안녕하세요, 내 상태가 변경되었습니다. 다시 렌더링해 주세요.“라고 알려줍니다. Blazor는 이벤트 핸들러 후에 자동으로 호출하지만 수동으로 호출해야 하는 경우도 있습니다. 일반적으로 일반 Blazor 이벤트 흐름 외부에서 상태가 변경되는 경우입니다.
private async Task StartPolling()
{
while (!cts.Token.IsCancellationRequested)
{
data = await Http.GetFromJsonAsync<Data>("api/data");
StateHasChanged(); // Manual call needed since this isn't a Blazor event
await Task.Delay(5000, cts.Token);
}
}
한 가지 중요한 참고 사항: Blazor Server의 백그라운드 스레드에서 업데이트하는 경우 InvokeAsync를 사용하세요.
await InvokeAsync(() =>
{
data = newData;
StateHasChanged();
});
전체 사진
모든 일이 일어나는 순서는 다음과 같습니다.
Component created
└─ SetParametersAsync
└─ OnInitialized / OnInitializedAsync
└─ OnParametersSet / OnParametersSetAsync
└─ Render
└─ OnAfterRender(firstRender: true)
Parameter change from parent
└─ SetParametersAsync
└─ OnParametersSet / OnParametersSetAsync
└─ ShouldRender?
└─ Render
└─ OnAfterRender(firstRender: false)
Component removed
└─ Dispose / DisposeAsync
이 흐름이 머릿속에 있으면 수명주기 문제 디버깅이 훨씬 쉬워집니다.
게시물이 마음에 드셨기를 바랍니다! @emimontesdeoca로 소셜 미디어를 통해 언제든지 저에게 연락해주세요.