I’m trying to reduce render count of a very heavy component which renders thousands of spans. I can’t apply virtualization because spans have different size.
<div contenteditable="true">
@foreach (var item in els)
{
<span @key="@item.Key" class="@item.cssClass">@item.String</span>
}
</div>
I made an attempt to replace span by a component which renders span and it works, but initial render time was taking too long.
I can’t do everything via js and disable rerender because root div may contain some other blazor components.
I need to apply some changes via JS without rerender, but at some time rerender happens and render state is corrupted. How can I notify blazor about changes in dom?
Or maybe I need to revert all changes made from js before render?
Change types:
- modify span’s textContent
- split span at some offset
- add span into div
Do I need to use something like node.dispatchEvent(new Event(‘change’))?
Are there any possible problems here?
2
Answers
The main problem with a single control with a lot of spans: it rerenders all spans which goes after an edited span, and Key doesn't work in that case. But if we put every span into separate control it dramatically reduces render speed.
So I decided to split spans to batches. I marked some spans as batch start and they are the same even after edits. So spans now grouped and only one group will be rerendered after edit. Actually we can change group size and select split granularity. And probably i would be better to change GetHashCode to some good hash.
SpanGroup.razor
Text.razor:
Blazor does not provide a built-in mechanism to notify the .NET runtime about DOM changes made by JavaScript, or to synchronize arbitrary JS changes with the Blazor app state. The framework is not designed for this and such practices are generally not recommended.
What you can do is call a C# .NET method with
JSInterop
after doing the JavaScript changes. Provide information about the changes to the .NET runtime and then for each of the possible change types do the following, is applicable:<span>
content: find the correct item in theels
collection and change itsString
property value.<span>
at some offset: replace the corresponding item in theels
collection with two new items with the correctString
values.<span>
to<div>
: add a new item toels
.When doing the server-side changes, you can override the
ShouldRender()
method of your component and temporarily disable re-rendering, because the DOM in the browser is already up-to-date.Update: Note that it may not be always possible to achieve perfect synchronization between the server-side app state with the DOM state. For example, Blazor uses empty HTML comments
<!-- -->
to distinguish components and their parts. The JavaScript DOM changes may break this mechanism. If this occurs, the only option is to remove the whole<div>
with all<span>
s temporarily with server-side code and re-render everything from scratch.