You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor WebAssembly中如何缓存RenderFragment输出?

在Blazor WebAssembly中缓存RenderFragment输出的可行方案

问题背景

需要实现二维数据虚拟化网格,避免DOM中存在大量元素导致性能下降。核心需求是:视图外的组件可以从DOM中移除,但保留其状态和渲染输出,当进入视图范围时无需重新初始化和渲染,直接恢复到DOM中。

已尝试的方案及局限

通过条件渲染(@if)、@key、RenderFragment等方式测试,发现所有从DOM移除后重新添加的组件都会触发重新初始化和渲染,无法保留原有实例状态;而用display:none隐藏组件虽能保留实例,但组件仍存在于DOM中,无法解决元素过多的性能问题。

测试用组件ConsoleWriter.razor:

<h3>ConsoleWriter @Name</h3>

@code {
    [Parameter]
    public string? Name { get; set; }

    protected override void OnInitialized() {
        Console.WriteLine($"ConsoleWriter {this.Name} initialized");
    }

    protected override void OnAfterRender(bool firstRender) {
        if(firstRender) {
            Console.WriteLine($"ConsoleWriter {this.Name} rendered");
        } else {
            Console.WriteLine($"ConsoleWriter {this.Name} re-rendered");
        }
    }

    protected override bool ShouldRender() => false;
}

测试页Index.razor(核心逻辑):

@page "/"

<p>Components are showing: @showComponents</p>
<button @onclick="() => this.showComponents=!this.showComponents">Toggle</button>

@* 隐藏但保留DOM,无法解决性能问题 *@
<div style="@(this.showComponents ? null : "display:none;")">
    <ConsoleWriter Name="OnlyHidden" />
</div>

@* 条件渲染,每次显示都会重新初始化组件 *@
@if(this.showComponents) {
    <ConsoleWriter Name="Default" />
    @consoleWriter
    <ConsoleWriter @key="consoleWriterKey" Name="WithKey" />
}

@code {
    private bool showComponents = false;
    private object consoleWriterKey = new object();

    private RenderFragment consoleWriter { get; } = builder => {
        builder.OpenComponent<ConsoleWriter>(0);
        builder.AddAttribute(1, nameof(ConsoleWriter.Name), "RenderFragment");
        builder.CloseComponent();
    };
}

可行解决方案思路

1. 组件实例缓存容器(保留交互性)

创建一个通用缓存容器组件,手动维护子组件的实例,即使子组件不在DOM中,也保留其状态和渲染逻辑,需要显示时直接复用实例。

实现缓存容器ComponentCache.razor

@typeparam TComponent where TComponent : ComponentBase, new()

@if (ShouldRender)
{
    @RenderCachedComponent()
}

@code {
    private TComponent? _cachedComponent;

    [Parameter] public bool ShouldRender { get; set; }
    [Parameter] public Dictionary<string, object?>? ComponentParameters { get; set; }

    protected override void OnInitialized()
    {
        // 初始化组件实例
        _cachedComponent = new TComponent();
        
        // 设置组件参数
        if (ComponentParameters != null)
        {
            var paramView = new ParameterView(ComponentParameters);
            _cachedComponent.SetParameterAsync(paramView).Wait();
        }
        
        // 触发组件初始化生命周期
        _cachedComponent.AttachToRenderTree(new RenderTreeBuilder(), 0);
        _cachedComponent.OnInitialized();
    }

    private RenderFragment RenderCachedComponent() => builder =>
    {
        _cachedComponent?.BuildRenderTree(builder);
    };

    // 更新组件参数方法(可选)
    public async Task UpdateParameters(Dictionary<string, object?> newParams)
    {
        if (_cachedComponent != null)
        {
            await _cachedComponent.SetParameterAsync(new ParameterView(newParams));
            StateHasChanged();
        }
    }
}

使用缓存容器

在Index.razor中替换条件渲染部分:

<ComponentCache TComponent="ConsoleWriter" ShouldRender="showComponents" 
                ComponentParameters="@(new Dictionary<string, object?> { 
                    { nameof(ConsoleWriter.Name), "CachedInstance" } 
                })"/>

此方案下,ConsoleWriter实例只会初始化一次,切换显示状态时不会重新触发OnInitialized,仅在首次渲染时触发OnAfterRender。

2. 静态HTML缓存(纯展示场景)

如果组件无需交互,可将组件的渲染结果缓存为HTML字符串,需要显示时直接输出静态HTML,完全避免组件实例的重复初始化。

扩展组件获取渲染HTML

在ConsoleWriter.razor的@code中添加:

public string GetRenderedHtml()
{
    var builder = new RenderTreeBuilder();
    BuildRenderTree(builder);
    var renderer = new HtmlRenderer(new ServiceCollection().BuildServiceProvider());
    return renderer.RenderToStringAsync(builder).Result;
}

在页面中缓存并使用

@page "/"

<p>Components are showing: @showComponents</p>
<button @onclick="ToggleShow">Toggle</button>

@if (showComponents)
{
    @((MarkupString)_cachedHtml)
}

@code {
    private bool showComponents = false;
    private string _cachedHtml = string.Empty;

    protected override void OnInitialized()
    {
        var component = new ConsoleWriter { Name = "HtmlCached" };
        component.OnInitialized();
        _cachedHtml = component.GetRenderedHtml();
    }

    private void ToggleShow() => showComponents = !showComponents;
}

该方案适合纯展示的虚拟化网格单元格,缺点是丢失组件的交互能力(如按钮点击、绑定更新等)。

3. 改造虚拟滚动组件(虚拟化网格专用)

参考Blazor官方Virtualize组件的实现逻辑,将其“销毁不可见项”的逻辑改为“缓存不可见项实例”:

  • 维护一个组件实例缓存池,当项离开视图时,将实例存入缓存池;
  • 当项进入视图时,从缓存池取出实例复用,而非创建新实例;
  • 仅当缓存池为空时,才创建新的组件实例。

这种方案最适合虚拟化网格场景,既能控制DOM元素数量,又能保留组件的交互性和状态。


内容的提问来源于stack exchange,提问作者Epiphaner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:39:52