skip to Main Content

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:

  1. modify span’s textContent
  2. split span at some offset
  3. add span into div

Do I need to use something like node.dispatchEvent(new Event(‘change’))?
Are there any possible problems here?

2

Answers


  1. Chosen as BEST ANSWER

    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

    @foreach (var item in Spans)
    {
        <span class="@item.CssClass">@item.String</span>
    }
    
    @code {
        [Parameter]
        public IReadOnlyList<Element> Spans { get; set; } = [];
    
    }
    

    Text.razor:

    @foreach (var group in els.Grouped())
    {
        <SpanGroup @key="@group.Key" Spans="group.Spans"></SpanGroup>
    }
    
    public static IEnumerable<Group> Grouped(this IEnumerable<Element> textElements, int groupSize = 10) {
    
        var accum = new List<Element>(groupSize);
        
        foreach (var element in textElements) {
            var isGroupStart = element.Key.GetHashCode() % groupSize == 0;
            if (isGroupStart && accum.Count > 0) {
    
                yield return new Group
                {
                    Spans = accum,
                    Key = accum[0].Key
                };
    
                accum = new List<Element>(groupSize);
            }
            accum.Add(element);
        }
        if (accum.Count > 0)
        {
            yield return new Group
            {
                Spans = accum,
                Key = accum[0].Key
            };
        }
    }
    

  2. 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:

    1. Modify <span> content: find the correct item in the els collection and change its String property value.
    2. Split <span> at some offset: replace the corresponding item in the els collection with two new items with the correct String values.
    3. Add <span> to <div>: add a new item to els.

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search