Can anyone explain the underpinnings of this topic?
To understand the update process you need to understand Components. I'll try and keep this short!
All components must implement IComponent. ComponentBase is an implementation of IComponent.
public interface IComponent
{
void Attach(RenderHandle renderHandle);
Task SetParametersAsync(ParameterView parameters);
}
The important bits of a RenderHandle are:
public readonly struct RenderHandle
{
public Dispatcher Dispatcher ....
//....
public void Render(RenderFragment renderFragment)
{
//....
_renderer.AddToRenderQueue(_componentId, renderFragment);
//...
}
}
And a RenderFragment is:
public delegate void RenderFragment(RenderTreeBuilder builder);
A Renderer manages the render process. It holds The DOM (what gets rendered by the Browser) represented as a RenderTree. When the Renderer attaches a component to the RenderTree, the Renderer creates a RenderHandle and passes it to the component by calling Attach. The Component uses this RenderHandle to communicate with the Renderer. The Renderer communicates with the component by calling SetParametersAsync.
Components "Render" by calling the Render method on the RenderHandle and passing a RenderFragment delegate.
Here's a simple render fragment:
protected RenderFragment HelloWorld => (RenderTreeBuilder builder) =>
{
builder.OpenElement(0, "div");
builder.AddContent(1, "Hello Blazor");
builder.CloseElement();
};
Calling Render on the RenderHandle DOES NOT render the component. It just places the render fragment on the Renderer's queue. When the Renderer runs the fragment it checks for component parameter changes referenced by other components. It calls SetParametersAsync on any components where their reference parameters have changed.
StateHasChanged is a ComponentBase method. StateHasChanged is called internally by the Blazor UI event handler, so you rarely need to call it manually. If you do, ask yourself why? Your logic may be wrong! It looks like this:
var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
await task;
StateHasChanged();
}
The main exception is a normal event handler. If the event updates data in the component you need to trigger a manual update by calling StateHasChanged. This is the standard pattern.
private void OnSomethingChanged(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
Notes:
StateHasChanged has a mechanism for detecting if a render fragment is already queued, so it doesn't queue multiple renders.
InvokeAsync ensures a task is run on the UI thread. It uses the Dispatcher provided on the RenderHandle.
- When the Renderer checks for parameter changes, any object is considered dirty as there's no simple way for the Renderer to check equality.
- The Renderer only services it's queue when it gets thread time to do so. If you run a long sequence of synchronous code in say a button click handler nothing happens till the synchronous code completes.
Delve into ComponentBase if you want to dig further - You can view the code here.