DejaComponentBase

Basiskomponente, die automatisch neu rendert, wenn die Query<T>- und Mutation<T>-Instanzen in ihrem Besitz den Zustand wechseln. Deklariere sie als Felder oder Properties; die Basis heftet sich bei der Initialisierung an jede an und löst sich beim Dispose.

public abstract class DejaComponentBase : ComponentBase, IAsyncDisposable

Member#

ComponentToken#

protected CancellationToken ComponentToken { get; }

Ein Token, das abgebrochen wird, wenn diese Komponente disposed wird. Die Queries und Mutations der Komponente verwenden es bereits — reiche es nur an Arbeit weiter, die Deja nicht für dich ausführt, etwa einen direkten API-Aufruf aus einem Event-Handler. Es gibt nichts zu disposen, und ein Zugriff nach dem Dispose liefert ein bereits abgebrochenes Token, statt zu werfen — eine späte Continuation sieht also einen Abbruch statt einer ObjectDisposedException.

Observe<TState>#

protected TState Observe<TState>(TState state) where TState : IDejaObservable

Heftet state an diese Komponente: rendert bei jeder Änderung neu, reicht ihm den registrierten DejaClient und das ComponentToken dieser Komponente und löst sich beim Dispose.

Wird selten direkt aufgerufen. Die Discovery führt das bei der Initialisierung für jede Query<T> / Mutation<T> in einem Feld oder einer Property aus, was die gewöhnliche Komponente abdeckt. Rufe es nur von Hand auf — für Zustand, den die Discovery nicht sehen kann: nach der Initialisierung erstellt (ein Event-Handler, eine Liste pro Zeile) oder in einer Komponente, deren OnInitialized-Override base.OnInitialized() auslässt. Nicht angehefteter Zustand fetcht trotzdem; er rendert die Komponente nur nie neu, erreicht nie den gemeinsamen Cache und wird beim Dispose nie abgebrochen.

Gibt state zurück, damit es inline verwendet werden kann; dieselbe Instanz zweimal anzuheften ist ein No-op. Wirft InvalidOperationException, wenn eine andere Komponente den Zustand bereits besitzt — gib dieser Komponente ihre eigene Instanz oder reiche die Daten als [Parameter] nach unten. Ausgearbeitete Beispiele findest du in Komponenten-Basisklasse.

OnInitialized#

protected override void OnInitialized()

Heftet den deklarierten Zustand an. Ein Override in einer abgeleiteten Komponente muss base.OnInitialized() aufrufen — bevor der eigene Zustand verwendet wird, üblicherweise in der ersten Zeile — sonst wird nichts angeheftet, und die Komponente rendert bei Zustandsänderungen nie neu.

Dispose (hook)#

protected virtual void Dispose()

Überschreibe dies für synchrones Aufräumen; läuft während des Disposals, vor dem DisposeAsync-Override und bevor die Basis sich vom beobachteten Zustand löst. Rufe base.Dispose() aus einem Override auf.

DisposeAsync (hook)#

protected virtual ValueTask DisposeAsync()

Überschreibe dies für asynchrones Aufräumen; läuft nach dem Dispose-Override, bevor die Basis sich löst. Rufe base.DisposeAsync() aus einem Override auf. Deklariere kein @implements IAsyncDisposable — die Basis implementiert es bereits, und dieses Override ist ihr Erweiterungspunkt.

Reihenfolge beim Disposal#

Wenn Blazor die Komponente entfernt, geht die Basis so vor:

  1. Sie bricht ComponentToken ab, sodass laufende Ausführungen den Abbruch beobachten, solange sie es noch können;
  2. sie führt den abgeleiteten Dispose()-Hook aus, dann den DisposeAsync()-Hook — Overrides sehen ihren Zustand dabei noch angeheftet;
  3. sie löst sich von allem beobachteten Zustand und disposed die Queries, die die Komponente erstellt hat — samt Abbruch ihrer Fetches — selbst wenn abgeleitetes Aufräumen wirft.

Leitplanke: neu deklariertes Disposal wirft#

Eine abgeleitete Komponente, die ihr eigenes Dispose oder DisposeAsync deklariert (typischerweise über @implements IDisposable / @implements IAsyncDisposable), wird im Konstruktor mit einer InvalidOperationException abgewiesen. Ein neu deklariertes DisposeAsync würde Dejas Disposal vollständig ersetzen — die Komponente würde ihre Anheftungen stillschweigend leaken und ihre Queries nie disposen; ein neu deklariertes Dispose würde von nichts mehr aufgerufen, weil Blazor nur über IAsyncDisposable disposed, sobald es vorhanden ist. Explizite Interface-Reimplementierungen werden ebenfalls erkannt; Helfer im Stil von Dispose(bool) sind erlaubt. Die Fehlermeldung nennt die Lösung: die Hooks per protected override überschreiben.

Leitplanke: ausgelassenes base.OnInitialized()#

Wer base.OnInitialized() auslässt, bekommt Code, der kompiliert und einmal rendert — dann hört die Komponente stillschweigend auf zu reagieren, weil nichts angeheftet wurde. Deja erkennt das beim ersten Render und meldet es einmalig als Konsolenfehler (über stderr, auf WebAssembly als console.error sichtbar), unter Nennung der Komponente und der Lösung.

Discovery-Details#

  • Felder und Properties werden einmal bei der Initialisierung gescannt, entlang der Typhierarchie.
  • [Parameter]-, [CascadingParameter]- und [Inject]-Member werden übersprungen — Parameter gehören dem Parent, der sie übergeben hat; ein Anheften würde sowohl den Listener-Slot des Parents stehlen (und werfen) als auch Zustand disposen, der der Komponente nicht gehört.
  • Zustand, der nach der Initialisierung zugewiesen wird, wird vom Scan nie erfasst — bei späteren Renders wird nichts erneut gescannt — er muss also an Observe() übergeben werden.
  • Der Scan ist trimming-annotiert und übersteht daher veröffentlichte, getrimmte WebAssembly-Builds.