ASP.NET Core Blazor 구성 요소 렌더링

구성 요소는 부모 구성 요소에 의해 구성 요소 계층 구조에 처음 추가될 때 ‘렌더링해야 합니다’. 구성 요소는 이 시점에만 렌더링해야 합니다. 구성 요소는 자체 논리 및 규칙에 따라 다른 시간에 ‘렌더링할 수 있습니다’.

ComponentBase의 렌더링 규칙

기본적으로 Razor 구성 요소는 다음 시간에 다시 렌더링을 트리거하는 논리를 포함하는 ComponentBase 기본 클래스에서 상속됩니다.

ComponentBase에서 상속된 구성 요소는 다음 중 하나에 해당하는 경우 매개 변수 업데이트로 인해 다시 렌더링을 건너뜁니다.

  • 모든 매개 변수 값은 알려진 변경 불가능 기본 형식(예: int, string, DateTime)이며 이전 매개 변수 세트가 설정된 이후 변경되지 않았습니다.
  • 구성 요소의 ShouldRender 메서드false를 반환합니다.

렌더링 흐름 제어

대부분의 경우 ComponentBase 규칙은 이벤트가 발생한 후 구성 요소 다시 렌더링의 올바른 하위 세트를 생성합니다. 개발자는 일반적으로 프레임워크에 다시 렌더링할 구성 요소와 이 구성 요소를 다시 렌더링할 시기를 알리는 수동 논리를 제공할 필요가 없습니다. 프레임워크 규칙의 전반적인 효과는 이벤트를 수신하는 구성 요소가 스스로 다시 렌더링되어 매개 변수 값이 변경되었을 수 있는 하위 구성 요소의 다시 렌더링을 재귀적으로 트리거합니다.

프레임워크 규칙의 성능 영향과 렌더링에 맞게 앱의 구성 요소 계층 구조를 최적화하는 방법에 관한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례를 참조하세요.

UI 새로 고침 중지(ShouldRender)

구성 요소를 렌더링할 때마다 ShouldRender가 호출됩니다. UI 새로 고침을 관리하려면 ShouldRender를 재정의합니다. 구현에서 true를 반환하는 경우 UI가 새로 고침됩니다.

ShouldRender를 재정의한 경우에도 처음에는 구성 요소가 항상 렌더링됩니다.

Pages/ControlRender.razor:

@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

ShouldRender와 관련된 성능 모범 사례에 대한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례을 참조하세요.

StateHasChanged를 호출하는 경우

StateHasChanged를 호출하여 언제든지 렌더링을 트리거할 수 있습니다. 그러나 불필요하게 StateHasChanged를 호출하지 않도록 주의해야 합니다. 해당 호출은 불필요한 렌더링 비용을 발생시키는 일반적인 실수입니다.

다음과 같은 경우 코드에서 StateHasChanged를 호출할 필요가 없습니다.

  • ComponentBase가 대부분 루틴 이벤트 처리기의 렌더링을 트리거한 후 동기적 또는 비동기적으로 이벤트를 정기적으로 처리하는 경우.
  • ComponentBase가 일반적인 수명 주기 이벤트의 렌더링을 트리거한 후 동기적 또는 비동기적 여부와 관계없이 OnInitialized 또는 OnParametersSetAsync와 같은 일반적인 수명 주기 논리를 구현하는 경우

그러나 이 문서의 다음 섹션에서 설명하는 사례에서는 StateHasChanged를 호출하는 것이 적합할 수 있습니다.

비동기 처리기는 여러 비동기 단계를 포함함

.NET에서 작업이 정의되는 방식으로 인해 Task의 수신자는 중간 비동기 상태가 아니라 최종 완료만 관찰할 수 있습니다. 따라서 ComponentBaseTask가 처음 반환될 경우와 Task가 마지막으로 완료될 경우에만 다시 렌더링을 트리거할 수 있습니다. 프레임워크는 다른 중간 지점에서 구성 요소를 다시 렌더링하는 것을 알 수 없습니다. 중간 지점에서 다시 렌더링하려면 해당 지점에서 StateHasChanged를 호출합니다.

클릭할 때마다 개수를 4번 업데이트하는 다음 CounterState1 구성 요소를 고려합니다.

  • 자동 렌더링은 currentCount의 처음과 마지막 증분 후에 수행됩니다.
  • currentCount가 증가하는 중간 처리 지점에서 프레임워크가 다시 렌더링을 자동으로 트리거하지 않는 경우 StateHasChanged를 호출하여 수동 렌더링을 트리거합니다.

Pages/CounterState1.razor:

@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Blazor 렌더링 및 이벤트 처리 시스템 외부에서 호출 수신

ComponentBase는 자체 수명 주기 메서드 및 Blazor 트리거 이벤트만 알 수 있습니다. ComponentBase는 코드에서 발생할 수 있는 다른 이벤트를 알 수 없습니다. 예를 들어, 사용자 지정 데이터 저장소에서 발생한 C# 이벤트는 Blazor에서 알 수 없습니다. 해당 이벤트가 다시 렌더링을 트리거하여 UI에 업데이트된 값을 표시하게 하려면 StateHasChanged를 호출합니다.

System.Timers.Timer를 사용하여 정기적으로 개수를 업데이트하고 StateHasChanged를 호출하여 UI를 업데이트하는 다음 CounterState2 구성 요소를 고려합니다.

  • OnTimerCallback은 Blazor 관리형 렌더링 흐름 또는 이벤트 알림 외부에서 실행됩니다. Blazor는 콜백에서 currentCount의 변경을 인식하지 못하므로 OnTimerCallbackStateHasChanged를 호출해야 합니다.
  • 구성 요소는 IDisposable을 구현합니다. 여기서 Timer는 프레임워크가 Dispose 메서드를 호출할 때 삭제됩니다. 자세한 내용은 ASP.NET Core Razor 구성 요소 수명 주기를 참조하세요.

콜백은 Blazor의 동기화 컨텍스트 외부에서 호출되기 때문에 구성 요소는 ComponentBase.InvokeAsync에서 OnTimerCallback의 논리를 래핑하여 렌더러의 동기화 컨텍스트로 이동해야 합니다. StateHasChanged는 렌더러의 동기화 컨텍스트에서만 호출할 수 있으며 이외의 경우에는 예외를 throw합니다. 이는 다른 UI 프레임워크의 UI 스레드로 마샬링하는 것과 같습니다.

Pages/CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

특정 이벤트가 다시 렌더링하는 하위 트리 외부에서 구성 요소를 렌더링하려면

UI에는 다음이 포함될 수 있습니다.

  1. 구성 요소로 이벤트 디스패치.
  2. 일부 상태 변경.
  3. 이벤트를 수신하는 구성 요소의 하위 항목이 아닌 완전히 다른 구성 요소 다시 렌더링.

해당 시나리오를 처리하는 한 가지 방법은 DI(종속성 삽입) 서비스와 같은 ‘상태 관리’ 클래스를 여러 구성 요소에 삽입하는 것입니다. 한 구성 요소가 상태 관리자에서 메서드를 호출하면 상태 관리자가 C# 이벤트를 발생시키고 독립 구성 요소가 이를 수신합니다.

해당 C# 이벤트는 Blazor 렌더링 파이프라인 외부에 있으므로 상태 관리자의 이벤트에 대한 응답으로 렌더링하려는 다른 구성 요소에서 StateHasChanged를 호출합니다.

해당 사례는 이전 섹션에서 System.Timers.Timer를 사용하는 이전 사례와 비슷합니다. 실행 호출 스택은 일반적으로 렌더러의 동기화 컨텍스트에 유지되므로 일반적으로 InvokeAsync를 호출할 필요가 없습니다. Task에서 ContinueWith를 호출하거나 ConfigureAwait(false)를 사용하여 Task를 대기하는 경우처럼 논리가 동기화 컨텍스트를 이스케이프하는 경우에만 InvokeAsync를 호출해야 합니다.

구성 요소는 부모 구성 요소에 의해 구성 요소 계층 구조에 처음 추가될 때 ‘렌더링해야 합니다’. 구성 요소는 이 시점에만 렌더링해야 합니다. 구성 요소는 자체 논리 및 규칙에 따라 다른 시간에 ‘렌더링할 수 있습니다’.

ComponentBase의 렌더링 규칙

기본적으로 Razor 구성 요소는 다음 시간에 다시 렌더링을 트리거하는 논리를 포함하는 ComponentBase 기본 클래스에서 상속됩니다.

ComponentBase에서 상속된 구성 요소는 다음 중 하나에 해당하는 경우 매개 변수 업데이트로 인해 다시 렌더링을 건너뜁니다.

  • 모든 매개 변수 값은 알려진 변경 불가능 기본 형식(예: int, string, DateTime)이며 이전 매개 변수 세트가 설정된 이후 변경되지 않았습니다.
  • 구성 요소의 ShouldRender 메서드false를 반환합니다.

렌더링 흐름 제어

대부분의 경우 ComponentBase 규칙은 이벤트가 발생한 후 구성 요소 다시 렌더링의 올바른 하위 세트를 생성합니다. 개발자는 일반적으로 프레임워크에 다시 렌더링할 구성 요소와 이 구성 요소를 다시 렌더링할 시기를 알리는 수동 논리를 제공할 필요가 없습니다. 프레임워크 규칙의 전반적인 효과는 이벤트를 수신하는 구성 요소가 스스로 다시 렌더링되어 매개 변수 값이 변경되었을 수 있는 하위 구성 요소의 다시 렌더링을 재귀적으로 트리거합니다.

프레임워크 규칙의 성능 영향과 렌더링에 맞게 앱의 구성 요소 계층 구조를 최적화하는 방법에 관한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례를 참조하세요.

UI 새로 고침 중지(ShouldRender)

구성 요소를 렌더링할 때마다 ShouldRender가 호출됩니다. UI 새로 고침을 관리하려면 ShouldRender를 재정의합니다. 구현에서 true를 반환하는 경우 UI가 새로 고침됩니다.

ShouldRender를 재정의한 경우에도 처음에는 구성 요소가 항상 렌더링됩니다.

Pages/ControlRender.razor:

@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

ShouldRender와 관련된 성능 모범 사례에 대한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례을 참조하세요.

StateHasChanged를 호출하는 경우

StateHasChanged를 호출하여 언제든지 렌더링을 트리거할 수 있습니다. 그러나 불필요하게 StateHasChanged를 호출하지 않도록 주의해야 합니다. 해당 호출은 불필요한 렌더링 비용을 발생시키는 일반적인 실수입니다.

다음과 같은 경우 코드에서 StateHasChanged를 호출할 필요가 없습니다.

  • ComponentBase가 대부분 루틴 이벤트 처리기의 렌더링을 트리거한 후 동기적 또는 비동기적으로 이벤트를 정기적으로 처리하는 경우.
  • ComponentBase가 일반적인 수명 주기 이벤트의 렌더링을 트리거한 후 동기적 또는 비동기적 여부와 관계없이 OnInitialized 또는 OnParametersSetAsync와 같은 일반적인 수명 주기 논리를 구현하는 경우

그러나 이 문서의 다음 섹션에서 설명하는 사례에서는 StateHasChanged를 호출하는 것이 적합할 수 있습니다.

비동기 처리기는 여러 비동기 단계를 포함함

.NET에서 작업이 정의되는 방식으로 인해 Task의 수신자는 중간 비동기 상태가 아니라 최종 완료만 관찰할 수 있습니다. 따라서 ComponentBaseTask가 처음 반환될 경우와 Task가 마지막으로 완료될 경우에만 다시 렌더링을 트리거할 수 있습니다. 프레임워크는 다른 중간 지점에서 구성 요소를 다시 렌더링하는 것을 알 수 없습니다. 중간 지점에서 다시 렌더링하려면 해당 지점에서 StateHasChanged를 호출합니다.

클릭할 때마다 개수를 4번 업데이트하는 다음 CounterState1 구성 요소를 고려합니다.

  • 자동 렌더링은 currentCount의 처음과 마지막 증분 후에 수행됩니다.
  • currentCount가 증가하는 중간 처리 지점에서 프레임워크가 다시 렌더링을 자동으로 트리거하지 않는 경우 StateHasChanged를 호출하여 수동 렌더링을 트리거합니다.

Pages/CounterState1.razor:

@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Blazor 렌더링 및 이벤트 처리 시스템 외부에서 호출 수신

ComponentBase는 자체 수명 주기 메서드 및 Blazor 트리거 이벤트만 알 수 있습니다. ComponentBase는 코드에서 발생할 수 있는 다른 이벤트를 알 수 없습니다. 예를 들어, 사용자 지정 데이터 저장소에서 발생한 C# 이벤트는 Blazor에서 알 수 없습니다. 해당 이벤트가 다시 렌더링을 트리거하여 UI에 업데이트된 값을 표시하게 하려면 StateHasChanged를 호출합니다.

System.Timers.Timer를 사용하여 정기적으로 개수를 업데이트하고 StateHasChanged를 호출하여 UI를 업데이트하는 다음 CounterState2 구성 요소를 고려합니다.

  • OnTimerCallback은 Blazor 관리형 렌더링 흐름 또는 이벤트 알림 외부에서 실행됩니다. Blazor는 콜백에서 currentCount의 변경을 인식하지 못하므로 OnTimerCallbackStateHasChanged를 호출해야 합니다.
  • 구성 요소는 IDisposable을 구현합니다. 여기서 Timer는 프레임워크가 Dispose 메서드를 호출할 때 삭제됩니다. 자세한 내용은 ASP.NET Core Razor 구성 요소 수명 주기를 참조하세요.

콜백은 Blazor의 동기화 컨텍스트 외부에서 호출되기 때문에 구성 요소는 ComponentBase.InvokeAsync에서 OnTimerCallback의 논리를 래핑하여 렌더러의 동기화 컨텍스트로 이동해야 합니다. StateHasChanged는 렌더러의 동기화 컨텍스트에서만 호출할 수 있으며 이외의 경우에는 예외를 throw합니다. 이는 다른 UI 프레임워크의 UI 스레드로 마샬링하는 것과 같습니다.

Pages/CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

특정 이벤트가 다시 렌더링하는 하위 트리 외부에서 구성 요소를 렌더링하려면

UI에는 다음이 포함될 수 있습니다.

  1. 구성 요소로 이벤트 디스패치.
  2. 일부 상태 변경.
  3. 이벤트를 수신하는 구성 요소의 하위 항목이 아닌 완전히 다른 구성 요소 다시 렌더링.

해당 시나리오를 처리하는 한 가지 방법은 DI(종속성 삽입) 서비스와 같은 ‘상태 관리’ 클래스를 여러 구성 요소에 삽입하는 것입니다. 한 구성 요소가 상태 관리자에서 메서드를 호출하면 상태 관리자가 C# 이벤트를 발생시키고 독립 구성 요소가 이를 수신합니다.

해당 C# 이벤트는 Blazor 렌더링 파이프라인 외부에 있으므로 상태 관리자의 이벤트에 대한 응답으로 렌더링하려는 다른 구성 요소에서 StateHasChanged를 호출합니다.

해당 사례는 이전 섹션에서 System.Timers.Timer를 사용하는 이전 사례와 비슷합니다. 실행 호출 스택은 일반적으로 렌더러의 동기화 컨텍스트에 유지되므로 일반적으로 InvokeAsync를 호출할 필요가 없습니다. Task에서 ContinueWith를 호출하거나 ConfigureAwait(false)를 사용하여 Task를 대기하는 경우처럼 논리가 동기화 컨텍스트를 이스케이프하는 경우에만 InvokeAsync를 호출해야 합니다.

구성 요소는 부모 구성 요소에 의해 구성 요소 계층 구조에 처음 추가될 때 ‘렌더링해야 합니다’. 구성 요소는 이 시점에만 렌더링해야 합니다. 구성 요소는 자체 논리 및 규칙에 따라 다른 시간에 ‘렌더링할 수 있습니다’.

ComponentBase의 렌더링 규칙

기본적으로 Razor 구성 요소는 다음 시간에 다시 렌더링을 트리거하는 논리를 포함하는 ComponentBase 기본 클래스에서 상속됩니다.

ComponentBase에서 상속된 구성 요소는 다음 중 하나에 해당하는 경우 매개 변수 업데이트로 인해 다시 렌더링을 건너뜁니다.

  • 모든 매개 변수 값은 알려진 변경 불가능 기본 형식(예: int, string, DateTime)이며 이전 매개 변수 세트가 설정된 이후 변경되지 않았습니다.
  • 구성 요소의 ShouldRender 메서드false를 반환합니다.

렌더링 흐름 제어

대부분의 경우 ComponentBase 규칙은 이벤트가 발생한 후 구성 요소 다시 렌더링의 올바른 하위 세트를 생성합니다. 개발자는 일반적으로 프레임워크에 다시 렌더링할 구성 요소와 이 구성 요소를 다시 렌더링할 시기를 알리는 수동 논리를 제공할 필요가 없습니다. 프레임워크 규칙의 전반적인 효과는 이벤트를 수신하는 구성 요소가 스스로 다시 렌더링되어 매개 변수 값이 변경되었을 수 있는 하위 구성 요소의 다시 렌더링을 재귀적으로 트리거합니다.

프레임워크 규칙의 성능 영향과 렌더링에 맞게 앱의 구성 요소 계층 구조를 최적화하는 방법에 관한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례를 참조하세요.

UI 새로 고침 중지(ShouldRender)

구성 요소를 렌더링할 때마다 ShouldRender가 호출됩니다. UI 새로 고침을 관리하려면 ShouldRender를 재정의합니다. 구현에서 true를 반환하는 경우 UI가 새로 고침됩니다.

ShouldRender를 재정의한 경우에도 처음에는 구성 요소가 항상 렌더링됩니다.

Pages/ControlRender.razor:

@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

ShouldRender와 관련된 성능 모범 사례에 대한 자세한 내용은 ASP.NET Core Blazor 성능 모범 사례을 참조하세요.

StateHasChanged를 호출하는 경우

StateHasChanged를 호출하여 언제든지 렌더링을 트리거할 수 있습니다. 그러나 불필요하게 StateHasChanged를 호출하지 않도록 주의해야 합니다. 해당 호출은 불필요한 렌더링 비용을 발생시키는 일반적인 실수입니다.

다음과 같은 경우 코드에서 StateHasChanged를 호출할 필요가 없습니다.

  • ComponentBase가 대부분 루틴 이벤트 처리기의 렌더링을 트리거한 후 동기적 또는 비동기적으로 이벤트를 정기적으로 처리하는 경우.
  • ComponentBase가 일반적인 수명 주기 이벤트의 렌더링을 트리거한 후 동기적 또는 비동기적 여부와 관계없이 OnInitialized 또는 OnParametersSetAsync와 같은 일반적인 수명 주기 논리를 구현하는 경우

그러나 이 문서의 다음 섹션에서 설명하는 사례에서는 StateHasChanged를 호출하는 것이 적합할 수 있습니다.

비동기 처리기는 여러 비동기 단계를 포함함

.NET에서 작업이 정의되는 방식으로 인해 Task의 수신자는 중간 비동기 상태가 아니라 최종 완료만 관찰할 수 있습니다. 따라서 ComponentBaseTask가 처음 반환될 경우와 Task가 마지막으로 완료될 경우에만 다시 렌더링을 트리거할 수 있습니다. 프레임워크는 다른 중간 지점에서 구성 요소를 다시 렌더링하는 것을 알 수 없습니다. 중간 지점에서 다시 렌더링하려면 해당 지점에서 StateHasChanged를 호출합니다.

클릭할 때마다 개수를 4번 업데이트하는 다음 CounterState1 구성 요소를 고려합니다.

  • 자동 렌더링은 currentCount의 처음과 마지막 증분 후에 수행됩니다.
  • currentCount가 증가하는 중간 처리 지점에서 프레임워크가 다시 렌더링을 자동으로 트리거하지 않는 경우 StateHasChanged를 호출하여 수동 렌더링을 트리거합니다.

Pages/CounterState1.razor:

@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Blazor 렌더링 및 이벤트 처리 시스템 외부에서 호출 수신

ComponentBase는 자체 수명 주기 메서드 및 Blazor 트리거 이벤트만 알 수 있습니다. ComponentBase는 코드에서 발생할 수 있는 다른 이벤트를 알 수 없습니다. 예를 들어, 사용자 지정 데이터 저장소에서 발생한 C# 이벤트는 Blazor에서 알 수 없습니다. 해당 이벤트가 다시 렌더링을 트리거하여 UI에 업데이트된 값을 표시하게 하려면 StateHasChanged를 호출합니다.

System.Timers.Timer를 사용하여 정기적으로 개수를 업데이트하고 StateHasChanged를 호출하여 UI를 업데이트하는 다음 CounterState2 구성 요소를 고려합니다.

  • OnTimerCallback은 Blazor 관리형 렌더링 흐름 또는 이벤트 알림 외부에서 실행됩니다. Blazor는 콜백에서 currentCount의 변경을 인식하지 못하므로 OnTimerCallbackStateHasChanged를 호출해야 합니다.
  • 구성 요소는 IDisposable을 구현합니다. 여기서 Timer는 프레임워크가 Dispose 메서드를 호출할 때 삭제됩니다. 자세한 내용은 ASP.NET Core Razor 구성 요소 수명 주기를 참조하세요.

콜백은 Blazor의 동기화 컨텍스트 외부에서 호출되기 때문에 구성 요소는 ComponentBase.InvokeAsync에서 OnTimerCallback의 논리를 래핑하여 렌더러의 동기화 컨텍스트로 이동해야 합니다. StateHasChanged는 렌더러의 동기화 컨텍스트에서만 호출할 수 있으며 이외의 경우에는 예외를 throw합니다. 이는 다른 UI 프레임워크의 UI 스레드로 마샬링하는 것과 같습니다.

Pages/CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

특정 이벤트가 다시 렌더링하는 하위 트리 외부에서 구성 요소를 렌더링하려면

UI에는 다음이 포함될 수 있습니다.

  1. 구성 요소로 이벤트 디스패치.
  2. 일부 상태 변경.
  3. 이벤트를 수신하는 구성 요소의 하위 항목이 아닌 완전히 다른 구성 요소 다시 렌더링.

해당 시나리오를 처리하는 한 가지 방법은 DI(종속성 삽입) 서비스와 같은 ‘상태 관리’ 클래스를 여러 구성 요소에 삽입하는 것입니다. 한 구성 요소가 상태 관리자에서 메서드를 호출하면 상태 관리자가 C# 이벤트를 발생시키고 독립 구성 요소가 이를 수신합니다.

해당 C# 이벤트는 Blazor 렌더링 파이프라인 외부에 있으므로 상태 관리자의 이벤트에 대한 응답으로 렌더링하려는 다른 구성 요소에서 StateHasChanged를 호출합니다.

해당 사례는 이전 섹션에서 System.Timers.Timer를 사용하는 이전 사례와 비슷합니다. 실행 호출 스택은 일반적으로 렌더러의 동기화 컨텍스트에 유지되므로 일반적으로 InvokeAsync를 호출할 필요가 없습니다. Task에서 ContinueWith를 호출하거나 ConfigureAwait(false)를 사용하여 Task를 대기하는 경우처럼 논리가 동기화 컨텍스트를 이스케이프하는 경우에만 InvokeAsync를 호출해야 합니다.