Blazor InteractiveServer 모드에서 삭제 후 목록이 갱신되지 않는 문제 해결

Blazor의 @rendermode InteractiveServer를 사용할 때, 서버 목록에서 항목을 삭제해도 화면에 즉시 반영되지 않고 다른 페이지로 이동했다가 돌아와야 사라지는 현상은 흔히 발생합니다. 이는 InteractiveServer 렌더링 모드의 상태 유지 메커니즘과 관련이 있습니다.

문제의 근본 원인

InteractiveServer 모드에서는 컴포넌트 인스턴스가 서버 메모리에 지속적으로 유지됩니다. 따라서 다음과 같은 특성이 나타납니다:

  • 컴포넌트 상태(예: servers 리스트)가 메모리에 보존됨
  • NavigationManager.NavigateTo("/servers") 호출 시 동일한 경로로의 네비게이션은 기존 인스턴스를 재사용함
  • OnInitialized와 같은 초기화 메서드가 다시 실행되지 않음

즉, 단순한 내비게이션은 UI를 새로 고치지 않으며, 데이터가 변경되었더라도 여전히 이전 상태의 servers 변수를 사용하게 됩니다.

왜 다른 페이지를 거쳐 돌아오면 업데이트될까?

다른 페이지로 이동했다가 돌아올 경우, Blazor는 해당 컴포넌트를 다시 연결(reconnection)하는 과정을 거칩니다. 이때 일부 생명주기 이벤트가 다시 트리거되어 데이터가 갱신되는 것처럼 보일 수 있지만, 이는 신뢰할 수 없는 동작이며 일관성 있게 작동하지 않을 수 있습니다.

해결 방법

방법 1: 삭제 후 데이터 직접 갱신 (권장)

삭제 작업 후, 데이터 저장소에서 최신 목록을 다시 가져와 상태를 업데이트해야 합니다.

private void RemoveServer(Server target)
{
    if (target.Id <= 0) return;

    // 데이터 계층에서 삭제
    ServerService.DeleteById(target.Id);

    // 현재 필터 조건에 따라 목록 갱신
    servers = string.IsNullOrEmpty(filterCity)
        ? ServerService.SearchByKeyword(searchTerm)
        : ServerService.GetListByLocation(filterCity);

    // UI 강제 재렌더링 요청
    StateHasChanged();
}

방법 2: 전체 페이지 리로드 강제 적용

기존 컴포넌트 상태를 완전히 무시하고 새로고침하려면 forceLoad: true 옵션을 사용합니다.

private void RemoveServer(Server target)
{
    ServerService.DeleteById(target.Id);
    NavigationManager.NavigateTo("/servers", forceLoad: true);
}

이 방식은 전통적인 페이지 전환과 유사하며, 모든 상태가 초기화되지만 성능에는 다소 부담이 될 수 있습니다.

방법 3: 상태 관리 서비스 활용 (최적화된 접근)

공유 상태를 중앙에서 관리하는 서비스를 도입하면 여러 컴포넌트 간 일관성을 유지할 수 있습니다.

// ServerState.cs
public class ServerState
{
    public List<Server> Items { get; private set; } = new();
    public event Action? Updated;

    public void Refresh(string? city = null, string? keyword = null)
    {
        Items = string.IsNullOrEmpty(city)
            ? ServerService.Search(keyword)
            : ServerService.GetByRegion(city);
        Updated?.Invoke();
    }

    public void Remove(int id)
    {
        ServerService.DeleteById(id);
        Refresh(); // 자동으로 목록 갱신
    }
}

// 컴포넌트에서 사용
@inject ServerState State

protected override void OnInitialized()
{
    State.Updated += StateHasChanged;
    State.Refresh(); // 초기 로드
}

private void RemoveServer(Server server) => State.Remove(server.Id);

public void Dispose() => State.Updated -= StateHasChanged;

방법 4: LocationChanged 이벤트 감지

URL 변화를 감지하여 필요한 경우 데이터를 다시 불러오는 방식입니다.

@implements IDisposable

[Inject] private NavigationManager NavManager { get; set; } = default!;

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        NavManager.LocationChanged += OnLocationChange;
    }
}

private void OnLocationChange(object? sender, LocationChangedEventArgs e)
{
    if (e.Location.Contains("servers", StringComparison.OrdinalIgnoreCase))
    {
        ReloadData();
        StateHasChanged();
    }
}

private void ReloadData()
{
    servers = string.IsNullOrEmpty(selectedCity)
        ? ServerService.Search(serverFilter)
        : ServerService.GetByCity(selectedCity);
}

public void Dispose()
{
    NavManager.LocationChanged -= OnLocationChange;
}

StateHasChanged()만으로 충분할까?

아닙니다. StateHasChanged()는 UI를 재렌더링하도록 요청할 뿐, 데이터 자체를 갱신하지 않습니다. 예를 들어:

private void RemoveServer(Server server)
{
    ServerService.DeleteById(server.Id);
    StateHasChanged(); // ❌ 실패 - servers 리스트는 여전히 이전 값 유지
}

이 경우, 삭제는 데이터 계층에서 수행되지만, 컴포넌트 내부의 servers 변수는 여전히 삭제된 항목을 포함하고 있으므로 UI에도 그대로 표시됩니다.

반면 아래 코드는 정상적으로 작동합니다:

private void RemoveServer(Server server)
{
    ServerService.DeleteById(server.Id);
    servers.RemoveAll(s => s.Id == server.Id); // ✅ 상태 직접 수정
    StateHasChanged(); // ✅ 이후 렌더링
}

요약 및 권장 사항

방법 신뢰성 성능 추천도
데이터 재조회 + StateHasChanged 높음 보통 ⭐⭐⭐⭐☆
상태 관리 서비스 매우 높음 좋음 ⭐⭐⭐⭐⭐
forceLoad: true 높음 낮음 ⭐⭐☆☆☆
LocationChanged 활용 중간 보통 ⭐⭐⭐☆☆

대규모 애플리케이션에서는 상태 관리 서비스를 사용하는 것이 장기적으로 더 나은 구조를 제공하며, 간단한 시나리오에서는 삭제 후 직접 상태를 갱신하는 방식이 가장 직관적입니다.

태그: Blazor InteractiveServer StateHasChanged NavigationManager 상태관리

7월 23일 20:24에 게시됨