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

WPF开发者求Blazor中类似ContentPresenter的组件绑定实现方案

Blazor 实现组件实例切换与复用的最优方案

需求背景

作为WPF开发者,我习惯了通过ContentPresenter绑定组件实例实现切换复用的模式:创建多个组件实例,切换显示不同实例的同时保留旧实例状态,现在需要在Blazor中实现相同效果。

WPF 实现示例

<ContentPresenter Content="{Binding LicensesText}" />
public LicensesText LicensesText { get; set; }

ctor()
{
    LicensesText licensesText = new LicensesText();
    licensesText.ClickedShowComparision += () => { Licenses = new Licences(); };
    LicensesText = licensesText;

    // 模拟延迟5秒
    List<LicensesText> list = new ();
    list.Add(licensesText);

    LicensesText licensesText1 = new LicensesText();
    licensesText1.ClickedShowComparision += () => { Licenses = new Licences(); };
    LicensesText = licensesText1;

    // 模拟延迟5秒
    LicensesText = list.First();
}

尝试的Blazor方案及问题

我原本期望Blazor能直接绑定组件实例,但这种方式无法正常工作:

SaySomething.razor

<h3>@Label</h3>

@code {
    public string Label { get; set; } = "Default Label";

    public void SetLabel(string label)
    {
        Label = label;
        StateHasChanged();
    }
}

Index.razor(无效尝试)

@SaySometing

@code
{
    public SaySometing SaySometing { get; set; } = new SaySometing();
}

我试过直接引用、DynamicComponent、RenderFragment三种方案,虽然能修改组件文本,但始终没找到切换已创建的ComponentBase实例并保留旧实例复用的正确方式,特求最优解。

我的尝试代码

@page "/"

@DynamicRender
<SaySometing @ref="SaySometing" />
<DynamicComponent Type="typeof(SaySometing)" @ref="DynamicComponent"></DynamicComponent>

<button @onclick="args => ChangeText(args)">Click Me!</button>

@code
{
    public SaySometing SaySometing { get; set; } = new SaySometing();

    public SaySometing SaySometing1 { get; set; } = new SaySometing();

    public DynamicComponent DynamicComponent { get; set; } = new DynamicComponent();

    private RenderFragment DynamicRender { get; set; }

    protected override Task OnInitializedAsync()
    {
        DynamicRender = CreateComponent();
        return base.OnInitializedAsync();
    }

    private RenderFragment CreateComponent() => builder =>
    {
        builder.OpenComponent(0, typeof(SaySometing));
        builder.AddComponentReferenceCapture(1, obj =>
        {
            SaySometing1 = (SaySometing) obj;
        });
        builder.CloseComponent();
    };

    private void ChangeText(MouseEventArgs args)
    {
        SaySometing.SetLabel("Hello From SaySomething!");
        SaySometing1.SetLabel("Gutten Tag From SaySomething1!");
        ((SaySometing?)DynamicComponent?.Instance)?.SetLabel("Dobrý den from DynamicComponent");
        StateHasChanged();
    }
}

最优解决方案

Blazor的组件渲染逻辑和WPF不同,无法直接赋值实例实现绑定,要达成实例切换+复用的目标,可采用以下两种方案:

方案一:RenderFragment 存储实例渲染逻辑,切换渲染目标

核心思路是提前为每个组件实例创建对应的RenderFragment,通过切换当前渲染的RenderFragment显示不同实例,所有实例状态会被保留。

修改后的Index.razor:

@page "/"

@CurrentComponent

<button @onclick="SwitchToInstance1">显示实例1</button>
<button @onclick="SwitchToInstance2">显示实例2</button>

@code {
    private RenderFragment CurrentComponent { get; set; }
    private SaySomething Instance1 { get; set; }
    private SaySomething Instance2 { get; set; }

    protected override void OnInitialized()
    {
        // 初始化默认显示实例1
        CurrentComponent = BuildInstanceFragment(instance => Instance1 = instance);
    }

    // 封装创建实例RenderFragment的方法
    private RenderFragment BuildInstanceFragment(Action<SaySomething> captureReference)
    {
        return builder =>
        {
            builder.OpenComponent<SaySomething>(0);
            builder.AddComponentReferenceCapture(1, obj => captureReference((SaySomething)obj));
            builder.CloseComponent();
        };
    }

    private void SwitchToInstance1()
    {
        CurrentComponent = BuildInstanceFragment(instance => Instance1 = instance);
        Instance1?.SetLabel("实例1已激活");
    }

    private void SwitchToInstance2()
    {
        CurrentComponent = BuildInstanceFragment(instance => Instance2 = instance);
        Instance2?.SetLabel("实例2已激活");
    }
}

方案二:通用组件容器+实例缓存,手动管理渲染

创建通用容器组件,内部维护实例缓存,通过ActiveKey切换显示目标实例,避免重复创建。

ComponentContainer.razor

@if (_activeEntry != null)
{
    @_activeEntry.RenderFragment
}

@code {
    private Dictionary<string, (ComponentBase Instance, RenderFragment RenderFragment)> _instanceCache = new();
    private (ComponentBase Instance, RenderFragment RenderFragment)? _activeEntry;

    [Parameter]
    public string ActiveKey { get; set; }

    [Parameter]
    public Type ComponentType { get; set; }

    [Parameter]
    public Dictionary<string, object> ComponentParameters { get; set; } = new();

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        if (string.IsNullOrEmpty(ActiveKey) || ComponentType == null)
        {
            _activeEntry = null;
            return;
        }

        // 缓存中无实例则创建
        if (!_instanceCache.TryGetValue(ActiveKey, out var entry))
        {
            RenderFragment fragment = builder =>
            {
                builder.OpenComponent(0, ComponentType);
                foreach (var param in ComponentParameters)
                {
                    builder.AddAttribute(1, param.Key, param.Value);
                }
                builder.AddComponentReferenceCapture(2, obj =>
                {
                    entry = ((ComponentBase)obj, fragment);
                    _instanceCache[ActiveKey] = entry;
                });
                builder.CloseComponent();
            };
            entry = (null, fragment);
            _instanceCache[ActiveKey] = entry;
        }

        _activeEntry = entry;
    }

    // 对外提供获取实例的方法
    public T GetInstance<T>(string key) where T : ComponentBase
    {
        return _instanceCache.TryGetValue(key, out var entry) ? (T)entry.Instance : null;
    }
}

使用容器的Index.razor

@page "/"
@ref="Container"

<ComponentContainer 
    ActiveKey="@CurrentActiveKey"
    ComponentType="typeof(SaySomething)" />

<button @onclick="() => SwitchInstance("Instance1")">切换到实例1</button>
<button @onclick="() => SwitchInstance("Instance2")">切换到实例2</button>

@code {
    private ComponentContainer Container { get; set; }
    private string CurrentActiveKey { get; set; } = "Instance1";

    private void SwitchInstance(string key)
    {
        CurrentActiveKey = key;
        var instance = Container.GetInstance<SaySomething>(key);
        instance?.SetLabel($"当前激活:{key}");
    }
}

方案说明

  • Blazor组件实例由渲染引擎管理,不能像WPF那样直接赋值绑定,必须通过RenderFragment或容器组件控制渲染逻辑。
  • 两种方案均支持实例复用:方案一更轻量,适合简单场景;方案二更通用,适配复杂组件或多实例管理场景。
  • 切换实例时,旧实例仅从DOM移除,并未被销毁,再次渲染时会恢复之前的状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 14:10:26