From 9e7edf24753c6b31c5165983639b456535d082cc Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Wed, 2 Jun 2021 09:00:05 -0500 Subject: [PATCH] Move Blazor ShouldRender section (#22475) --- .../ControlRender.razor | 0 .../ControlRender.razor | 0 .../ControlRender.razor | 0 .../ControlRender.razor | 0 aspnetcore/blazor/components/lifecycle.md | 24 +----- aspnetcore/blazor/components/rendering.md | 26 ++++++- .../webassembly-performance-best-practices.md | 76 ++++++++----------- 7 files changed, 54 insertions(+), 72 deletions(-) rename aspnetcore/blazor/common/samples/3.x/BlazorSample_Server/Pages/{lifecycle => rendering}/ControlRender.razor (100%) rename aspnetcore/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/{lifecycle => rendering}/ControlRender.razor (100%) rename aspnetcore/blazor/common/samples/5.x/BlazorSample_Server/Pages/{lifecycle => rendering}/ControlRender.razor (100%) rename aspnetcore/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/{lifecycle => rendering}/ControlRender.razor (100%) diff --git a/aspnetcore/blazor/common/samples/3.x/BlazorSample_Server/Pages/lifecycle/ControlRender.razor b/aspnetcore/blazor/common/samples/3.x/BlazorSample_Server/Pages/rendering/ControlRender.razor similarity index 100% rename from aspnetcore/blazor/common/samples/3.x/BlazorSample_Server/Pages/lifecycle/ControlRender.razor rename to aspnetcore/blazor/common/samples/3.x/BlazorSample_Server/Pages/rendering/ControlRender.razor diff --git a/aspnetcore/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor b/aspnetcore/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor similarity index 100% rename from aspnetcore/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor rename to aspnetcore/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor diff --git a/aspnetcore/blazor/common/samples/5.x/BlazorSample_Server/Pages/lifecycle/ControlRender.razor b/aspnetcore/blazor/common/samples/5.x/BlazorSample_Server/Pages/rendering/ControlRender.razor similarity index 100% rename from aspnetcore/blazor/common/samples/5.x/BlazorSample_Server/Pages/lifecycle/ControlRender.razor rename to aspnetcore/blazor/common/samples/5.x/BlazorSample_Server/Pages/rendering/ControlRender.razor diff --git a/aspnetcore/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor b/aspnetcore/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor similarity index 100% rename from aspnetcore/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor rename to aspnetcore/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 0bf85e1781..be922523a0 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -40,7 +40,7 @@ The `Render` lifecycle: 1. Avoid further rendering operations on the component: * After the first render. - * When [`ShouldRender`](#suppress-ui-refreshing-shouldrender) is `false`. + * When [`ShouldRender`](xref:blazor/components/rendering#suppress-ui-refreshing-shouldrender) is `false`. 1. Build the render tree diff (difference) and render the component. 1. Await the DOM to update. 1. Call [`OnAfterRender{Async}`](#after-component-render-onafterrenderasync). @@ -207,28 +207,6 @@ Even if you return a from is called each time a component is rendered. Override to manage UI refreshing. If the implementation returns `true`, the UI is refreshed. - -Even if is overridden, the component is always initially rendered. - -`Pages/ControlRender.razor`: - -::: moniker range=">= aspnetcore-5.0" - -[!code-razor[](~/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor)] - -::: moniker-end - -::: moniker range="< aspnetcore-5.0" - -[!code-razor[](~/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/lifecycle/ControlRender.razor)] - -::: moniker-end - -For more information on performance best practices pertaining to , see . - ## State changes (`StateHasChanged`) notifies the component that its state has changed. When applicable, calling causes the component to be rerendered. diff --git a/aspnetcore/blazor/components/rendering.md b/aspnetcore/blazor/components/rendering.md index fabdb5e884..4d9d369a8b 100644 --- a/aspnetcore/blazor/components/rendering.md +++ b/aspnetcore/blazor/components/rendering.md @@ -25,9 +25,7 @@ By default, Razor components inherit from the skip rerenders due to parameter updates if either of the following are true: * All of the parameter values are of known immutable primitive types, such as `int`, `string`, `DateTime`, and haven't changed since the previous set of parameters were set. -* The component's method returns `false`. - -For more information on , see . +* The component's [`ShouldRender` method](#suppress-ui-refreshing-shouldrender) returns `false`. ## Control the rendering flow @@ -35,6 +33,28 @@ In most cases, conventions For more information on the performance implications of the framework's conventions and how to optimize an app's component hierarchy for rendering, see . +## Suppress UI refreshing (`ShouldRender`) + + is called each time a component is rendered. Override to manage UI refreshing. If the implementation returns `true`, the UI is refreshed. + +Even if is overridden, the component is always initially rendered. + +`Pages/ControlRender.razor`: + +::: moniker range=">= aspnetcore-5.0" + +[!code-razor[](~/blazor/common/samples/5.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor)] + +::: moniker-end + +::: moniker range="< aspnetcore-5.0" + +[!code-razor[](~/blazor/common/samples/3.x/BlazorSample_WebAssembly/Pages/rendering/ControlRender.razor)] + +::: moniker-end + +For more information on performance best practices pertaining to , see . + ## When to call `StateHasChanged` Calling allows you to trigger a render at any time. However, be careful not to call unnecessarily, which is a common mistake that imposes unnecessary rendering costs. diff --git a/aspnetcore/blazor/webassembly-performance-best-practices.md b/aspnetcore/blazor/webassembly-performance-best-practices.md index 9b50183f0c..40509cbfff 100644 --- a/aspnetcore/blazor/webassembly-performance-best-practices.md +++ b/aspnetcore/blazor/webassembly-performance-best-practices.md @@ -33,57 +33,41 @@ The last two steps of this sequence continue recursively down the component hier If you want to interrupt this process and prevent rendering recursion into a particular subtree, then you can either: * Ensure that all parameters to a certain component are of primitive immutable types (for example, `string`, `int`, `bool`, `DateTime`, and others). The built-in logic for detecting changes automatically skips rerendering if none of these parameter values have changed. If you render a child component with ``, where `CustomerId` is an `int` value, then it isn't rerendered except when `item.CustomerId` changes. -* If you need to accept nonprimitive parameter values, such as custom model types, event callbacks, or values, then you can override to control the decision about whether to render, which is described in the [Use of `ShouldRender`](#use-of-shouldrender) section. +* If you need to accept nonprimitive parameter values, such as custom model types, event callbacks, or values or if authoring a UI-only component that doesn't change after the initial render (regardless of any parameter values), override to control the decision about whether to render. The following example uses private fields to track the necessary information to detect changes. The value of `shouldRender` is based on checking for any kind of change or mutation that should prompt a rerender. `prevOutboundFlightId` and `prevInboundFlightId` track information for the next potential update: + + ```razor + @code { + [Parameter] + public FlightInfo OutboundFlight { get; set; } + + [Parameter] + public FlightInfo InboundFlight { get; set; } + + private int prevOutboundFlightId; + private int prevInboundFlightId; + private bool shouldRender; + + protected override void OnParametersSet() + { + shouldRender = OutboundFlight.FlightId != prevOutboundFlightId + || InboundFlight.FlightId != prevInboundFlightId; + + prevOutboundFlightId = OutboundFlight.FlightId; + prevInboundFlightId = InboundFlight.FlightId; + } + + protected override bool ShouldRender() => shouldRender; + } + ``` + + In the preceding example, an event handler may also set `shouldRender` to `true` so that the component is rerendered after the event. For most components, this level of manual control isn't necessary. You should only be concerned about skipping rendering subtrees if those subtrees are particularly expensive to render and are causing UI lag. For more information, see . + + For general information on `ShouldRender`, see . By skipping rerendering of whole subtrees, you may be able to remove the vast majority of the rendering cost when an event occurs. You may wish to factor out child components specifically so that you can skip rerendering that part of the UI. This is a valid way to reduce the rendering cost of a parent component. -#### Use of `ShouldRender` - -If authoring a UI-only component that never changes after the initial render (regardless of any parameter values), configure to return `false`: - -```razor -@code { - protected override bool ShouldRender() => false; -} -``` - -If the component only requires rerendering when its parameter values mutate in particular ways, then you can use private fields to track the necessary information to detect changes. In the following example, `shouldRender` is based on checking for any kind of change or mutation that should prompt a rerender. `prevOutboundFlightId` and `prevInboundFlightId` track information for the next potential update: - -```razor -@code { - [Parameter] - public FlightInfo OutboundFlight { get; set; } - - [Parameter] - public FlightInfo InboundFlight { get; set; } - - private int prevOutboundFlightId; - private int prevInboundFlightId; - private bool shouldRender; - - protected override void OnParametersSet() - { - shouldRender = OutboundFlight.FlightId != prevOutboundFlightId - || InboundFlight.FlightId != prevInboundFlightId; - - prevOutboundFlightId = OutboundFlight.FlightId; - prevInboundFlightId = InboundFlight.FlightId; - } - - protected override bool ShouldRender() => shouldRender; - - // Note that -} -``` - -In the preceding code, an event handler may also set `shouldRender` to `true` so that the component is rerendered after the event. - -For most components, this level of manual control isn't necessary. You should only be concerned about skipping rendering subtrees if those subtrees are particularly expensive to render and are causing UI lag. - -For more information, see . - ::: moniker range=">= aspnetcore-5.0" ### Virtualization