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
相关产品推荐
相关产品推荐

