Version Information:
- .NET SDK: 7.0.202
- ASP.NET Core Blazor WebAssembly: 7.0.4
- MudBlazor: 6.2.0
Der Beispiel-Code für diesen Artikel findet sich hier.
In der Entwicklung von Webanwendungen, und so auch in der Entwicklung von Blazor WebAssembly Single Page Applications (SPAs), ist es wichtig, die Laufzeit-Performance der Anwendung immer im Auge zu behalten und wenn nötig, zu optimieren. Daher schauen wir uns in diesem Artikel die Optimierungsmöglichkeiten einer Blazor-Komponente etwas genauer an. Bevor wir aber mit der Optimierung von Komponenten beginnen können, ist es wichtig zu verstehen, welchen Lebenszyklus eine Komponente beim Rendern durchläuft und wie dieser ausgelöst werden kann.
Render-Lebenszyklus einer Komponente
Lebenszyklus
Der Lebenszyklus einer Blazor-Komponente beginnt, wenn sie auf der Seite gerendert wird, d.h. sie wird zum ersten Mal sichtbar.
In der obigen Abbildung sehen wir die verschiedenen Schritte, die durchlaufen werden, sobald das Rendering einer Komponente angestoßen wird.
Im ersten Schritt wird geprüft, ob es sich um das initiale Rendern der Komponente handelt, oder andernfalls die Komponente neu gerendert werden soll. Dies wird anhand von zwei Optionen geprüft:
- Innerhalb der Basisklasse einer Blazor-Komponente, der
ComponentBase
-Klasse, wird geprüft, ob es sich um das erste Rendern der Komponente handelt. - Handelt es sich nicht um das erste Rendering, wird die Methode
ShouldRender()
, welche im nächsten Abschnitt näher erläutert wird, der Basisklasse aufgerufen. Die Methode gibt einenbool
-Wert zurück der angibt, ob Render-Prozess durchgeführt werden soll. Der Default Rückgabe-Wert isttrue
.
Trifft keine der beiden Optionen zu, wird der Render-Prozess beendet (2a). Andernfalls wird ein neuer Render-Tree erstellt (2b). Der Render-Tree beschreibt die HTML-Elemente des HTML-Dokuments, welche in der Blazor-Welt aktualisiert werden sollen. Danach wird dieser an das Document Object Model (DOM) weitergeben. Anhand des Render-Trees werden die Änderungen im DOM aktualisiert. Ist das Update abgeschlossen, werden schließlich die Methoden OnAfterRender
und OnAfterRenderAsync
aufgerufen und der Prozess ist beendet.
Hinweis: Der Render-Tree beinhaltet nur die Änderungen der Komponente, die sich im Vergleich zum aktuell gerenderten Zustand im DOM geändert haben. Dadurch wird nicht bei jedem Render-Prozess, das DOM-Element vollständig ersetzt, sondern nur die aktuellen Änderungen werden im DOM aktualisiert.
Weitere Informationen und Details über den Lebenszyklus von Blazor-Komponenten finden sich bei meinem Kollegen Pawel Gerr in seinem englischen Artikel Blazor Components Deep Dive – Lifecycle Is Not Always Straightforward.
Was kann ein Re-Rendering auslösen?
Nachdem wir gesehen haben, welchen Prozess eine Komponente beim Rendern durchläuft, schauen wir uns jetzt mal an, was den Render-Prozess einer Komponente auslösen kann:
- Das erste Rendern findet, wie zu erwarten, beim Initialisieren einer Blazor-Komponente statt.
Das zweite Szenario, bei der die Komponente neu gerendert wird, ist es, wenn sich Parameter ändern. Dabei wird zwischen eigenen Parametern und Parametern der übergeordneten Komponente unterschieden:
SetParametersAsync
: Diese Methode wird aufgerufen, sobald Parameter des übergeordneten Elements geändert wurden oder durch das Setzten eines Route-Parameters in der URL.OnParametersSet
/OnParametersSetAsync
: Diese Methoden werden aufgerufen, sobald sich die Parameter der Komponente ändern oder die übergeordnete Komponente neu gerendert wird.
- Wird ein DOM-Event ausgelöst, durch beispielsweise ein
onlclick
-Event, wird die Komponente ebenfalls neu gerendert. Aber nicht nur die Komponente, bei der das Event ausgelöst wurde, sondern auch alle untergeordneten Elemente werden neu gerendert - Zuletzt kann der Prozess über die Methode
StateHasChanged
aus der BasisklasseComponentBase
der Render-Prozess angestoßen werden. Die MethodeStateHasChanged()
benachrichtigt die Komponente, dass sich der aktuelle Status geändert hat. Dies führt dann dazu, dass die Komponente neu gerendert wird.
Betrachtet man die unterschiedlichen Szenarien, wird ersichtlich, dass der Render-Prozess sehr oft getriggert werden kann. Doch ist das wirklich immer notwendig? Im weiteren Verlauf dieses Artikels schauen wir uns mögliche Optionen an, wie wir den Render-Prozess einer Komponente optimieren können.
Optimierung des Render-Prozesses
ShouldRender überschreiben
Die erste Option, die wir nutzen können den Render-Prozess zu unterbinden, ist die Methode ShouldRender
zu überschreiben. Wie wir in der obigen Abbildung gesehen haben, wird in Schritt 2, sobald es sich nicht um das initiale Rendering handelt und die Methode ShouldRender
auch false
zurückgibt, der Render-Prozess beendet.
Die Methode ShouldRender
ist eine Methode der Basisklasse ComponentBase
. Der Default Rückgabewert der Methode ist true
. Infolgedessen wird bei jedem Render-Vorgang der Prozess durchgeführt. Nehmen wir beispielsweise ein Formular. Wird in einem Formular ein Feld geändert, wird dadurch der Render-Prozess der Komponenten angestoßen. Dies führt dazu, dass zusätzlich auch all die Komponenten neu gerendert werden, die sich im Formular befinden. In einem Formular ist es natürlich wichtig zu beachten, ob das aktuelle Feld Abhängigkeiten zu anderen Feldern hat. Hat das Feld keine Abhängigkeiten, so muss auch nicht bei jeder Änderung des Feldes neu gerendert werden. Um dies zu vermeiden, können wir die ShouldRender
-Methode überschreiben.
// CustomInputText.razor.cs
private int _valueHashCode;
private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender;
protected override bool OnParametersSet()
{
var lastHashCode = _valueHashCode;
_valueHashCode = Value?.GetHashCode() ?? 0;
_shouldRender = _valueHashCode != lastHashCode;
}
Im obigen Code-Beispiel sehen wir, dass anhand des HashCode
die Property Value
der Komponente geprüft wird, ob sich diese geändert hat. Ist dies nicht der Fall, gibt die Methode false
zurück und der Render-Prozess wird beendet. Sollte die Komponente keine Properties haben, die sich ändern, kann hier natürlich auch immer direkt false
zurückgegeben werden.
Dies ist eine Möglichkeit den Render-Prozess frühzeitig zu beenden, wenn es nicht zwingend notwendig ist die Komponente neu zu rendern. Im nächsten Abschnitt schauen wir uns an, wie wir beim Auslösen eines Events den Render-Prozess optimieren können.
IHandleEvent implementieren
Wird ein Event geworfen, wird die Methode StateHasChanged
der ComponentBase
-Klasse aufgerufen. Das führt dazu, dass der Render-Prozess der Komponente angestoßen wird. Dies hat zwar den Vorteil, dass der Entwickler die Methode StateHasChanged
nicht selbst aufrufen muss, wenn ein Event angestoßen wird. Jedoch hat das auch den Nachteil, dass die Komponente auch ohne jegliche Änderungen neu gerendert wird. Um in diesen Prozess einzugreifen, kann das Interface IHandleEvent
implementiert werden. Über das Interface wird die Methode HandleEventAsync
implementiert, die bei einem Event der Komponente aufgerufen wird.
// HandleEventInputText.razor.cs
public partial class HandleEventInputText : ComponentBase, IHandleEvent
{
private bool _preventRender;
public Task HandleEventAsync(EventCallbackWorkItem item, object? arg)
{
try
{
var task = item.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
if (!_preventRender)
{
StateHasChanged();
}
return shouldAwaitTask
? CallStateHasChangedOnAsyncCompletion(task, _supressRender)
: Task.CompletedTask;
}
finally
{
_preventRender = false;
}
}
private async Task CallStateHasChangedOnAsyncCompletion(Task task, bool preventRender)
{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}
throw;
}
if (!preventRender)
{
StateHasChanged();
}
}
void PreventRender()
{
_preventRender = true;
}
}
Im obigen Code-Beispiel sehen wir die Klasse HandleEventInputText
, die von der Basisklasse ComponentBase
ableitet und das Interface IHandleEvent
implementiert. In der Methode HandleEventAsync
wird anhand der Property _preventRender
geprüft, ob die Komponente gerendert werden soll oder nicht. Dadurch haben wir in der Komponente die Möglichkeit, bei einem Event die Methode PreventRender
aufzurufen, wie wir im obigen Code-Beispiel beim Event oninput
sehen können. Infolgedessen bekommt die Property _preventRender
den Wert true
zugewiesen. Solange die Property den Wert true
besitzt, wird der Render-Prozess nicht angestoßen.
Event-Utilities
Eine weitere Variante das erneute Rendern bei Events zu vermeiden, ist der Einsatz der Hilfsklasse EventUtil
, die von Microsoft empfohlen wird.
public static class EventUtil
{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action AsNonRenderingEventHandler(
Action callback)
=> new SyncReceiver(callback).Invoke;
public static Func AsNonRenderingEventHandler(Func callback)
=> new AsyncReceiver(callback).Invoke;
public static Func AsNonRenderingEventHandler(
Func callback)
=> new AsyncReceiver(callback).Invoke;
private record SyncReceiver(Action callback)
: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver(Action callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver(Func callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }
private record ReceiverBase : IHandleEvent
{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg) =>
item.InvokeAsync(arg);
}
}
Die Hilfsklasse stellt die Methode AsNonRenderingEventHandler
bereit. Die Methode kann genutzt werden, um die Func
oder Action
zwar auszuführen, aber das erneute Rendering des EventHandlers nicht auszulösen.
Beim Klick auf diesen Button wird kein neues Rendering ausgelöst
Im obigen Code-Beispiel, sehen wir einen Button, der einen OnClick
-EventHandler, mit Hilfe der Methode AsNonRenderingEventHandler
, aufruft. Ähnlich wie im vorherigen Abschnitt wird auch hier das IHandleEvent
-Interface eingesetzt, wie es beim Datentyp ReceiverBase
zu sehen ist. Der Unterschied zum vorherigen Ansatz liegt darin, dass das Rendering direkt vermieden wird und es kann vorab nicht geprüft werden, ob gerendert werden soll oder nicht.
Im nächsten Abschnitt schauen wir uns an, wie wir mit dem Einsatz von JavaScript ein DOM-Event verzögern können, um dadurch die Anzahl der Render-Vorgänge zu reduzieren.
Debounce DOM-Event
Hier vorab ein kleiner Hinweis: In diesem Abschnitt wird Javascript-Interop eingesetzt. Da wir uns in diesem Artikel aber auf die Optimierung von Komponenten fokussieren, gehe ich nicht weiter auf JS-Interop ein. Mehr Informationen hierzu finden sich hier.
Ein aus der JavaScript-Welt bekanntes Verfahren, um das Anstoßen von Events zu verzögern, ist das Debouncing. Beim Debounce-Verfahren wird der EventCallback erst dann ausgeführt, wenn nach einer bestimmten Zeitspanne keine neuen Events mehr geworfen werden. Das heißt, wenn wir z.B. in einem Textfeld tippen, wird das Event oninput
erst dann gefeuert, sobald aufgehört wurde zu tippen und eine selbst gesetzte Zeitspanne abgelaufen ist. Dies hat den Vorteil, dass das Event nur einmal ausgeführt wird. Andernfalls würden wir ohne Debouncing für jeden einzelnen Tastenanschlag ein einzelnes Event bekommen das wir behandeln würden, und das jedes Mal ein Re-Rendering auslösen könnte.
Um das Debounce Verfahren in einer Blazor-WebAssembly-Komponente einzusetzen, müssen wir sowohl im C#-Code als auch im JavaScript-Code, Methoden implementieren.
C#-/Razor-Code
// DebounceTextArea.razor.cs
public partial class DebounceTextArea : IDisposable
{
//... Code above
[Inject] public IJSRuntime JS { get; set; }
private IJSObjectReference _module;
private ElementReference _textareaElement;
private DotNetObjectReference _selfReference;
// JavaScript Event registrieren
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (_module is not null)
{
_module = await JS.InvokeAsync("import",
"./Components/DebounceTextArea.razor.js");
}
_selfReference = DotNetObjectReference.Create(this);
// Event wird nach 500 ms geworfen
var minInterval = 500;
// JavaScript-Code aufrufen mit dem HTML-Element,
// einer ObjectReferenz und dem Timeout bis das Event geworfen wird
await _module.InvokeVoidAsync("onDebounceInput",
_textareaElement, _selfReference, minInterval);
}
Console.WriteLine("Debounced TextArea: After Render called.");
}
// Methode die vom JavaScript-Code aufgerufen werden kann. Als Parameter
// wird der aktuelle Wert der TextArea übergeben.
[JSInvokable]
public void HandleOnInput(string value)
{
Console.WriteLine($"TextChanged {Value}. JS Value {value}");
if (Value != value)
{
StateHasChanged();
}
}
//... Code below
public void Dispose() => _selfReference?.Dispose();
}
Betrachten wir zunächst den C#-Code im obigen Code-Beispiel. In der Methode OnAfterRenderAsync
wird beim initialen Rendering mit Hilfe von JS-Interop die Funktion onDebounceInput
im JavaScript aufgerufen. Zum Aufrufen werden zwei Referenzen erwartet:
Die erste Referenz ist eine Objektreferenz der Komponente selbst, die mit Hilfe der Klasse
DotNetObjectReference
erstellt werden kann.Hinweis: Wird ein
Disposeable
-Objekt in einer Komponente genutzt, muss das InterfaceIDisposable
implementiert werden. In der MethodeDispose
müssen dann alle Objekte zerstört werden.- Die zweite Referenz verweist auf das HTML-Objekt. Im Code-Beispiel wird hierfür im HTML-Code das Attribut
@ref
genutzt, um eineElementReference
im C#-Code zu erstellen.
Die anderen beiden Parameter, um die JavaScript-Methode aufzurufen, sind natürlich der Name der Methode und das Intervall für das Verzögern des Events.
Weiter schauen wir uns die HandleOnInput
-Methode näher an. Diese wird vom JavaScript-Code aufgerufen, sobald das Event geworfen wurde. Damit der JavaScript-Code die Methode aufrufen kann, wird das JSInvokable
-Attribut benötigt. In der Methode selbst wird lediglich die Methode StateHasChanged
aufgerufen, sobald sich der Wert der Property Value
geändert hat.
JavaScript-Code
Um die Methode HandleOnInput
, die wir im obigen Abschnitt gesehen haben, aufrufen zu können, müssen wir die JavaScript-Funktion onDebounceInput
hinzufügen. Diese wird dann aus dem C#-Code via JS-Interop aufgerufen.
// DebounceTextArea.razor.js
export function onDebounceInput(elem, component, interval) {
elem.addEventListener('input', debounce(e => {
component.invokeMethodAsync('HandleOnInput', e.target.value);
}, interval));
}
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
Wie wir im obigen Code-Beispiel sehen, wird im JavaScript-Code die Funktion onDebounceInput
implementiert. Diese finden wir auch im C# Code wieder, wenn wir nochmal auf die Methode OnAfterRenderAsync
schauen. Hier wird nachdem alle Parameter definiert wurden, um die Funktion mit der folgenden Code-Zeile aufgerufen:
// DebounceTextArea.razor.cs
await JS.InvokeVoidAsync(
"onDebounceInput", _textareaElement, _selfReference, minInterval
);
Nachdem die C#-Methode InvokeVoidAsync
aufgerufen wurde, wird via JSInterop die JavaScript-Funktion onDebounceInput
aufgerufen. Die Funktion registriert einen EventListener
der auf das Event input
hört. Mit Hilfe der debounce
-Funktion, wird der Aufruf der C#-Methode HandleOnInput
solange verzögert, bis der Interval abgelaufen ist, nachdem aufgehört wurde in das Textfeld zu schreiben.
Hinweis: Um den Effekt im Video gut erkennen zu können, sollte das Video im Fullscreen abgespielt werden.
Bevor wir uns nun im nächsten Abschnitt mit dem Darstellen von Listen beschäftigen, hier noch ein Hinweis: Das Überschreiben von ShouldRender
oder das Implementieren des Interface IHandleEvent
kann mit dem Debounce verfahren auch kombiniert werden.
Listenoptimierung
Virtualize-Komponente
Nachdem wir nun Möglichkeiten gesehen haben, um den Render-Prozess von Komponenten in Blazor WebAssembly zu optimieren, schauen wir uns jetzt an was passiert, wenn wir eine Liste rendern. Als Beispiel nutzen wir hier eine Liste, in der wir einzelne Einträge selektieren können.
Wie wir im vorherigen Abschnitt des Artikels schon gesehen haben, wird bei einem Event wie z.b. ein OnClick
der Render-Prozess angestoßen. Infolgedessen haben wir bei einer Liste das Problem, dass bei jeder Click
auf ein Listen-Element, alle Einträge neu gerendert werden. Zusätzlich müssen bei einem for
-Loop von Anfang an alle Einträge in den DOM geladen werden. Zur Optimierung kann die Virtualize
-Komponente, die vom Framework zur Verfügung gestellt wird, genutzt werden. Die Komponente hat den Vorteil, dass nur die Komponenten geladen werden, die sich im Sichtbereich der Anwendung befinden. Was dazu führt das, auch wenn über die API die Daten nicht nach und nach geladen werden können, nicht alle Einträge direkt dem DOM hinzugefügt werden. Zusätzlich kann über den Parameter OverscanCount
vor und nach dem Sichtbereich eine gewisse Anzahl an Einträgen im DOM vorgeladen werden.
Seit .NET 7 gibt es die Möglichkeit den SpacerElement
-Parameter zu setzen. Mit diesem Parameter ist wird angegeben welches Element genutzt werden soll, bei welchem die berechnete Höhe angegeben wird. Der Default-Wert ist ein div
-element. Wie wir in der folgenden Abbildung sehen wird dieses Element vor und nach den im DOM gerenderten Komponenten hinzugefügt.
Dies kann hilfreich sein, um z.B. in einer Tabelle den Wert tr
-Element zu ändern oder in einer Liste auf ein li
-Element.
// Contributions.razor.cs
public partial class Contributions
{
[Inject] public ContributionService ContributionService { get; set; }
private async ValueTask> LoadContributions(
ItemsProviderRequest request)
{
var maxCount = 200;
var numConfs = Math.Min(request.Count, maxCount - request.StartIndex);
var contributions =
await ContributionService.GetContributionsAsync(request.StartIndex, numConfs,
request.CancellationToken);
return new ItemsProviderResult(contributions, maxCount);
}
}
Im obigen Code-Beispiel sehen wir die Virtualize
-Komponente. Die Liste an Personen wird mit dem ItemProvider
geladen. Damit beim Scrollen der Liste keine zu große Verzögerung auftritt, werden mit dem OverscanCount
vor und nach dem Sichtbereich 10 Einträge vorgehalten. Der ItemProvider
bietet die Möglichkeit, die Daten via Lazy-Loading nachzuladen. Im obigen Code-Beispiel, sehen wir die Methode LoadContributions
, die immer dann aufgerufen wird, sobald neue Einträge geladen werden müssen. Können die Daten nicht schnell genug nachgeladen werden, durch beispielsweise einer langsamen API oder einer schlechten Internetverbindung, bietet die Virtualize
-Komponente die Möglichkeit an, einen Placeholder einzusetzen. Dieser wird so lange angezeigt, bis die nächsten Einträge geladen wurden.
Durch den Einsatz der Virtualize
-Komponente haben wir also die Möglichkeit die Performance zu optimieren. Jedoch kann dies nicht zwingend mit den Optimierungen der vorherigen Abschnitte verbunden werden. Das liegt daran, dass Komponenten, die nicht im Sichtbereich liegen oder vorgeladen wurden, aus dem DOM entfernt wurden. Wenn diese wieder geladen werden, handelt es sich um das Initialisieren der Komponente, was automatisch zu einem Render-Prozess führt.
Daher ist eine wichtige Voraussetzung zum Einsetzten der Virtualize
-Komponente, dass nicht alle Einträge einer Liste im DOM gerendert sein müssen.
EXPERIMENTAL: QuickGrid
Eine weitere Variante eine Liste bzw. eine Tabelle zu virtualisieren ist das QuickGird, welches mit .NET 7 als expertimentelles Package vorgestellt wurde. Der Fokus der Komponente liegt in der performanten Darstellung von Daten in Form eines Grids.
Ein weiterer herausragender Vorteil dieser Komponente ist es, dass das Grid sehr stark angepasst werden kann:
- Die UI kann via CSS Klassen und eigenem Theme angepasst werden.
- Die Spalten können durch ein eigenes Template ersetzt werden.
- Für die Sortierung der Spalten kann eine eigene Funktion hinzugefügt werden.
- uvm.
Wie auch schon in der vorherigen Komponente, kann auch hier die Virtualisierung durch den Parameter Virtualize aktiviert werden. Dies hat den gleichen Effekt wie es im vorherigen Kapitel beschrieben wurde. Ein weiteres Feature ist die Option, Paging hinzuzufügen. So dass die Tabelle Seitenweise dargestellt werden kann und die Seiten über eine Navigation gewechselt werden können.
Im folgenden Codebeispiel sehen wir, wie die Komponente eingesetzt werden kann.
//Contributions.razor
Page:
@if (pagination.TotalItemCount.HasValue)
{
for (var pageIndex = 0; pageIndex <= pagination.LastPageIndex; pageIndex++)
{
var capturedIndex = pageIndex;
}
}
//Contributions.razor.cs
private GridItemsProvider? _contributionsProvider;
private PaginationState pagination = new PaginationState { ItemsPerPage = 100 };
protected override async Task OnInitializedAsync()
{
_contributionsProvider = async req =>
{
var count = await _contributionService.GetContributionCountAsync(req.CancellationToken);
var response = await _contributionService.GetContributionsAsync(req.StartIndex, req.Count ?? 100, req.CancellationToken);
return GridItemsProviderResult.From(
items: response ?? new(),
totalItemCount: count);
};
pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
await base.OnInitializedAsync();
}
private async Task GoToPageAsync(int pageIndex) =>
await pagination.SetCurrentPageIndexAsync(pageIndex);
private string? PageButtonClass(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "current" : null;
private string? AriaCurrentValue(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "page" : null;
Mehr Information bzgl. Anpassung und Nutzung der Komponente finden sich hier.
Hinweis: Die Komponente befindet sich in .NET 7 zwar noch in einem experimentellen Zustand, jedoch wurde bereits bestätigt, dass in der Version .NET 8 das QuickGrid teil des Frameworks sein wird (das dazugehörige Github Issue findet sich hier).
Fazit
ShouldRender
oder das Implementieren des Interface IHandleEvent
konnten wir sehen, dass dadurch bereits viele Render-Vorgänge eingespart wurden.
Doch auch wenn ein Rendering nicht vermieden werden kann, haben wir durch den Einsatz von JavaScript bei einem Event, die Anzahl der Render-Vorgänge minimieren können.
Um Listen performant darstellen zu können, haben wir gesehen das sowohl mit der Virtualize
-Komponente als auch der QuickGrid
-Komponente, bereits viele Funktionen eingesetzt werden konnten, wie beispielsweise das Lazy-Loading, Paging oder eine Ladeanimation als Placeholder. Dies trägt nicht nur zur besseren Performance bei, sondern auch einer erhöhten Usability der Anwendung.
Daher lässt sich abschließend sagen, dass wenn man beim Entwickeln von Komponenten darauf achtet, die Notwendigkeit des (Re-)Renderns so gering wie möglich zu halten, die Performance deutlich gesteigert werden kann.