RazorASP.NET Core元件轉譯

本文說明 Razor ASP.NET Core Blazor 應用程式中的元件轉譯,包括何時呼叫 StateHasChanged 以手動觸發元件來轉譯。

元件 必須先 由父元件新增至元件階層時轉譯。 這是元件必須轉譯的唯一時間。 元件 可能會 根據自己的邏輯和慣例在其他時間轉譯。

的轉譯慣例 ComponentBase

根據預設, Razor 元件會繼承自 ComponentBase 基類,其中包含在下列時間觸發重新呈現的邏輯:

如果下列任一項成立,則繼承自 ComponentBase 略過重新呈現的元件會因為參數更新而繼承:

控制轉譯流程

在大部分情況下, ComponentBase 慣例會導致在事件發生後重新呈現元件的正確子集。 開發人員通常不需要提供手動邏輯,才能告訴架構要重新呈現哪些元件,以及何時要重新呈現它們。 架構慣例的整體效果是接收事件重新呈現本身的元件,以遞迴方式觸發其參數值可能已變更的子代元件重新呈現。

如需架構慣例效能影響以及如何優化應用程式元件階層以進行轉譯的詳細資訊,請參閱ASP.NET Core Blazor 效能最佳做法

隱藏 UI 重新整理 ShouldRender ()

ShouldRender 每次轉譯元件時呼叫 。 覆寫 ShouldRender 以管理 UI 重新整理。 如果實作傳 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 會觸發大部分常式事件處理常式的轉譯。
  • 實作一般生命週期邏輯,例如 OnInitializedOnParametersSetAsync ,無論是同步還是非同步,因為 ComponentBase 會觸發一般生命週期事件的轉譯。

不過,在本文下列各節所述的案例中,呼叫 StateHasChanged 可能很合理:

非同步處理常式牽涉到多個非同步階段

由於工作在 .NET 中定義的方式,接收者 Task 只能觀察其最終完成,而不是中繼非同步狀態。 因此, ComponentBase 只有在第一次傳回 時,以及最後完成時 TaskTask ,才能觸發重新呈現。 架構不知道在其他中繼點重新呈現元件,例如當 傳回一系列中繼 Task 中的資料IAsyncEnumerable<T> 。 如果您想要在中繼點重新呈現,請在這些點呼叫 StateHasChanged

請考慮下列 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

請考慮使用 下列 CounterState2 元件 System.Timers.Timer 定期更新計數,並呼叫 StateHasChanged 來更新 UI:

  • OnTimerCallback 會在任何 Blazor 受控轉譯流程或事件通知之外執行。 因此,必須呼叫 StateHasChangedOnTimerCallback 因為 Blazor 不知道回呼中的變更 currentCount
  • 元件會實作 IDisposable ,其中 Timer 會在架構呼叫 Dispose 方法時處置 。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

由於回呼是在同步處理內容之外 Blazor 叫用的,所以元件必須包裝 中的 ComponentBase.InvokeAsync 邏輯 OnTimerCallback ,才能將它移至轉譯器的同步處理內容。 這相當於封送處理至其他 UI 架構中的 UI 執行緒。 StateHasChanged 只能從轉譯器的同步處理內容呼叫,否則會擲回例外狀況:

System.InvalidOperationException:'目前的執行緒未與發送器相關聯。 使用 InvokeAsync () 在觸發轉譯或元件狀態時,將執行切換至發送器。

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 。 只有在邏輯逸出同步處理內容時,才需要呼叫 InvokeAsync ,例如在 上 Task 呼叫 ContinueWith 或使用 等候 TaskConfigureAwait(false) 。 如需詳細資訊,請參閱 從轉譯和事件處理系統外部 Blazor 接收呼叫 一節。

元件 必須先 由父元件新增至元件階層時轉譯。 這是元件必須轉譯的唯一時間。 元件 可能會 根據自己的邏輯和慣例在其他時間轉譯。

的轉譯慣例 ComponentBase

根據預設, Razor 元件會繼承自 ComponentBase 基類,其中包含在下列時間觸發重新呈現的邏輯:

如果下列任一項成立,則繼承自 ComponentBase 略過重新呈現的元件會因為參數更新而繼承:

Blazor的架構會使用一組內建規則來偵測變更,隨時可能會變更。 如需詳細資訊,請參閱ChangeDetection ASP.NET Core 參考來源中的 API

注意

.NET 參考來源的文件連結通常會載入存放庫的預設分支,這表示下一版 .NET 的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

控制轉譯流程

在大部分情況下, ComponentBase 慣例會導致在事件發生後重新呈現元件的正確子集。 開發人員通常不需要提供手動邏輯,以告訴架構要重新呈現哪些元件,以及何時重新呈現這些元件。 架構慣例的整體效果是接收事件重新呈現本身的元件,以遞迴方式觸發其參數值可能已變更的子系元件重新呈現。

如需架構慣例效能影響以及如何優化應用程式元件階層以進行轉譯的詳細資訊,請參閱ASP.NET Core Blazor 效能最佳做法

隱藏 UI 重新整理 ShouldRender ()

ShouldRender 每次轉譯元件時都會呼叫 。 覆寫 ShouldRender 以管理 UI 重新整理。 如果實作傳 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 會觸發大部分常式事件處理常式的轉譯。
  • 實作一般生命週期邏輯,例如 OnInitializedOnParametersSetAsync ,無論是同步還是非同步,因為 ComponentBase 會觸發一般生命週期事件的轉譯。

不過,在本文下列各節所述的案例中呼叫 StateHasChanged 可能很合理:

非同步處理常式牽涉到多個非同步階段

由於工作在 .NET 中定義的方式,接收 Task 者只能觀察其最終完成,而不是中繼非同步狀態。 因此, ComponentBase 只有在第一次傳回 時,以及最後完成時 TaskTask ,才能觸發重新呈現。 架構不知道在其他中繼點重新呈現元件,例如, IAsyncEnumerable<T> 當 傳回一系列中繼 Task 中的資料時。 如果您想要在中繼點重新呈現,請在這些點呼叫 StateHasChanged

請考慮下列 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

請考慮使用 下列 CounterState2 元件 System.Timers.Timer 定期更新計數,並呼叫 StateHasChanged 來更新 UI:

  • OnTimerCallback 會在任何 Blazor 受管理的轉譯流程或事件通知之外執行。 因此,必須呼叫 StateHasChangedOnTimerCallback 因為 Blazor 不知道回呼中的變更 currentCount
  • 元件會實作 IDisposable ,其中 Timer 會在架構呼叫 Dispose 方法時處置 。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

因為回呼是在 同步處理內容外部 Blazor 叫用,所以元件必須包裝 中的 ComponentBase.InvokeAsync 邏輯 OnTimerCallback ,才能將它移至轉譯器的同步處理內容。 這相當於封送處理至其他 UI 架構中的 UI 執行緒。 StateHasChanged 只能從轉譯器的同步處理內容呼叫,否則會擲回例外狀況:

System.InvalidOperationException:'目前的執行緒未與發送器相關聯。 在觸發轉譯或元件狀態時,使用 InvokeAsync () 將執行切換至發送器。

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 案例。 由於執行呼叫堆疊通常會保留在轉譯器的同步處理內容上,因此通常不需要呼叫 InvokeAsyncInvokeAsync只有在邏輯逸出同步處理內容時,才需要呼叫 ,例如在 上 Task 呼叫 ContinueWith 或使用 等候 TaskConfigureAwait(false) 。 如需詳細資訊,請參閱從轉 譯和事件處理系統外部 Blazor 接收呼叫 一節。

元件 一次由父元件新增至元件階層時,必須轉譯這些元件。 這是元件必須轉譯的唯一時間。 元件 可能會 根據自己的邏輯和慣例在其他時間呈現。

的轉譯慣例 ComponentBase

根據預設, Razor 元件會繼承自 ComponentBase 基類,其中包含在下列時間觸發重新呈現的邏輯:

如果下列任一項成立,則繼承自 ComponentBase 的元件會因為參數更新而略過重新呈現:

Blazor的架構會使用一組內建規則來偵測變更,隨時可能會變更。 如需詳細資訊,請參閱ChangeDetection ASP.NET Core 參考來源中的 API

注意

.NET 參考來源的文件連結通常會載入存放庫的預設分支,這表示下一版 .NET 的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

控制轉譯流程

在大部分情況下, ComponentBase 慣例會導致在事件發生後重新呈現元件的正確子集。 開發人員通常不需要提供手動邏輯,以告訴架構要重新呈現哪些元件,以及何時重新呈現這些元件。 架構慣例的整體效果是接收事件重新呈現本身的元件,以遞迴方式觸發其參數值可能已變更的子系元件重新呈現。

如需架構慣例效能影響以及如何優化應用程式元件階層以進行轉譯的詳細資訊,請參閱ASP.NET Core Blazor 效能最佳做法

隱藏 UI 重新整理 ShouldRender ()

ShouldRender 每次轉譯元件時都會呼叫 。 覆寫 ShouldRender 以管理 UI 重新整理。 如果實作傳 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 會觸發大部分常式事件處理常式的轉譯。
  • 實作一般生命週期邏輯,例如 OnInitializedOnParametersSetAsync ,無論是同步還是非同步,因為 ComponentBase 會觸發一般生命週期事件的轉譯。

不過,在本文下列各節所述的案例中,呼叫 StateHasChanged 可能很合理:

非同步處理常式牽涉到多個非同步階段

由於工作在 .NET 中定義的方式,接收者 Task 只能觀察其最終完成,而不是中繼非同步狀態。 因此, ComponentBase 只有在第一次傳回 時,以及最後完成時 TaskTask ,才能觸發重新呈現。 架構不知道在其他中繼點重新呈現元件,例如當 傳回一系列中繼 Task 中的資料IAsyncEnumerable<T> 。 如果您想要在中繼點重新呈現,請在這些點呼叫 StateHasChanged

請考慮下列 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

請考慮使用 下列 CounterState2 元件 System.Timers.Timer 定期更新計數,並呼叫 StateHasChanged 來更新 UI:

  • OnTimerCallback 會在任何 Blazor 受控轉譯流程或事件通知之外執行。 因此,必須呼叫 StateHasChangedOnTimerCallback 因為 Blazor 不知道回呼中的變更 currentCount
  • 元件會實作 IDisposable ,其中 Timer 會在架構呼叫 Dispose 方法時處置 。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

由於回呼是在同步處理內容之外 Blazor 叫用的,所以元件必須包裝 中的 ComponentBase.InvokeAsync 邏輯 OnTimerCallback ,才能將它移至轉譯器的同步處理內容。 這相當於封送處理至其他 UI 架構中的 UI 執行緒。 StateHasChanged 只能從轉譯器的同步處理內容呼叫,否則會擲回例外狀況:

System.InvalidOperationException:'目前的執行緒未與發送器相關聯。 使用 InvokeAsync () 在觸發轉譯或元件狀態時,將執行切換至發送器。

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 。 只有在邏輯逸出同步處理內容時,才需要呼叫 InvokeAsync ,例如在 上 Task 呼叫 ContinueWith 或使用 等候 TaskConfigureAwait(false) 。 如需詳細資訊,請參閱 從轉譯和事件處理系統外部 Blazor 接收呼叫 一節。

元件 必須先 由父元件新增至元件階層時轉譯。 這是元件必須轉譯的唯一時間。 元件 可能會 根據自己的邏輯和慣例在其他時間轉譯。

的轉譯慣例 ComponentBase

根據預設, Razor 元件會繼承自 ComponentBase 基類,其中包含在下列時間觸發重新呈現的邏輯:

如果下列任一項成立,則繼承自 ComponentBase 略過重新呈現的元件會因為參數更新而繼承:

控制轉譯流程

在大部分情況下, ComponentBase 慣例會導致在事件發生後重新呈現元件的正確子集。 開發人員通常不需要提供手動邏輯,才能告訴架構要重新呈現哪些元件,以及何時要重新呈現它們。 架構慣例的整體效果是接收事件重新呈現本身的元件,以遞迴方式觸發其參數值可能已變更的子代元件重新呈現。

如需架構慣例效能影響以及如何優化應用程式元件階層以進行轉譯的詳細資訊,請參閱ASP.NET Core Blazor 效能最佳做法

隱藏 UI 重新整理 ShouldRender ()

ShouldRender 每次轉譯元件時呼叫 。 覆寫 ShouldRender 以管理 UI 重新整理。 如果實作傳 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 會觸發大部分常式事件處理常式的轉譯。
  • 實作一般生命週期邏輯,例如 OnInitializedOnParametersSetAsync ,無論是同步還是非同步,因為 ComponentBase 會觸發一般生命週期事件的轉譯。

不過,在本文下列各節所述的案例中,呼叫 StateHasChanged 可能很合理:

非同步處理常式牽涉到多個非同步階段

由於工作在 .NET 中定義的方式,接收者 Task 只能觀察其最終完成,而不是中繼非同步狀態。 因此, ComponentBase 只有在第一次傳回 時,以及最後完成時 TaskTask ,才能觸發重新呈現。 架構不知道在其他中繼點重新呈現元件,例如當 傳回一系列中繼 Task 中的資料IAsyncEnumerable<T> 。 如果您想要在中繼點重新呈現,請在這些點呼叫 StateHasChanged

請考慮下列 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

請考慮使用 下列 CounterState2 元件 System.Timers.Timer 定期更新計數,並呼叫 StateHasChanged 來更新 UI:

  • OnTimerCallback 會在任何 Blazor 受管理的轉譯流程或事件通知之外執行。 因此,必須呼叫 StateHasChangedOnTimerCallback 因為 Blazor 不知道回呼中的變更 currentCount
  • 元件會實作 IDisposable ,其中 Timer 會在架構呼叫 Dispose 方法時處置 。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

因為回呼是在 同步處理內容外部 Blazor 叫用,所以元件必須包裝 中的 ComponentBase.InvokeAsync 邏輯 OnTimerCallback ,才能將它移至轉譯器的同步處理內容。 這相當於封送處理至其他 UI 架構中的 UI 執行緒。 StateHasChanged 只能從轉譯器的同步處理內容呼叫,否則會擲回例外狀況:

System.InvalidOperationException:'目前的執行緒未與發送器相關聯。 在觸發轉譯或元件狀態時,使用 InvokeAsync () 將執行切換至發送器。

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 案例。 由於執行呼叫堆疊通常會保留在轉譯器的同步處理內容上,因此通常不需要呼叫 InvokeAsyncInvokeAsync只有在邏輯逸出同步處理內容時,才需要呼叫 ,例如在 上 Task 呼叫 ContinueWith 或使用 等候 TaskConfigureAwait(false) 。 如需詳細資訊,請參閱從轉 譯和事件處理系統外部 Blazor 接收呼叫 一節。