Blazor动态包装组件时如何正确捕获子组件引用?
如何捕获Blazor包装器内子组件的引用而非包装器本身?
我想实现编写 <Component/> 时,自动渲染为:
<Wrapper> <Component /> </Wrapper>
通过自定义IComponentActivator实现了该功能,但使用@ref捕获组件引用时抛出InvalidCastException——因为@ref试图获取ComponentIWantToBeInWrapper类型实例,但实际得到的是Wrapper实例。请问有没有办法让@ref捕获包装器内的子组件而非包装器本身?
最小复现代码示例
ComponentActivator.cs
public class ComponentActivator : IComponentActivator { public IComponent CreateInstance(Type componentType) { if(componentType == typeof(Component)) { return (IComponent)Activator.CreateInstance(typeof(DivWrapper<>).MakeGenericType(componentType))!; } if (componentType.IsGenericType && componentType.GetGenericTypeDefinition() == typeof(Dummy<>)) { var innerType = componentType.GetGenericArguments()[0]; return (IComponent)Activator.CreateInstance(innerType)!; } return (IComponent)Activator.CreateInstance(componentType)!; } }
DivWrapper.razor
@typeparam T where T : ComponentBase <div style="border: 1px solid red"> <Dummy T="T" @attributes=Attrs /> </div> @code { [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> Attrs { get; set; } = default!; }
Dummy.cs
public class Dummy<T> : ComponentBase where T : ComponentBase { }
Page.razor
@page "/" <Component /> @* <Component @ref=_ref /> 这行代码会导致应用崩溃 *@
解决方案
核心思路
问题根源是IComponentActivator返回的是DivWrapper实例,Blazor的@ref会直接绑定这个顶层实例。要让@ref指向内部子组件,需要让包装器拦截@ref参数,将内部子组件的引用传递给绑定委托。
具体实现
- 修改DivWrapper组件,拦截@ref参数并暴露内部子组件引用
@typeparam T where T : ComponentBase <div style="border: 1px solid red"> <Dummy T="T" @attributes=Attrs @ref="_innerComponentRef" /> </div> @code { private T? _innerComponentRef; [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> Attrs { get; set; } = default!; // 暴露内部子组件引用,供外部获取 public T? InnerComponent => _innerComponentRef; public override async Task SetParametersAsync(ParameterView parameters) { // 拦截Blazor内部的@ref参数标识 if (parameters.TryGetValue("__internal__ref", out var refValue) && refValue is Action<object?> setRef) { // 将内部子组件引用传递给@ref绑定委托 setRef(_innerComponentRef); // 移除该参数,避免传递给子组件 var newParams = parameters.Remove("__internal__ref"); await base.SetParametersAsync(newParams); } else { await base.SetParametersAsync(parameters); } } }
- 修改Dummy组件,传递@ref捕获委托到真实子组件
public class Dummy<T> : ComponentBase where T : ComponentBase { [Parameter] public object? __internal__ref { get; set; } [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenComponent<T>(0); // 传递所有属性 if (AdditionalAttributes != null) { foreach (var attr in AdditionalAttributes) { builder.AddAttribute(1, attr.Key, attr.Value); } } // 传递@ref捕获委托到真实子组件 if (__internal__ref is Action<object?> setRef) { builder.AddComponentReferenceCapture(2, setRef); } builder.CloseComponent(); } }
- 页面中正常使用@ref
@page "/" <Component @ref="_componentRef" /> @code { private Component? _componentRef; protected override void OnAfterRender(bool firstRender) { if (firstRender && _componentRef != null) { // 此时_componentRef是内部的Component实例,而非DivWrapper Console.WriteLine($"获取到子组件引用:{_componentRef.GetType().Name}"); } } }
关键说明
- Blazor编译
@ref时会生成__internal__ref参数,携带设置引用的委托 - 包装器通过拦截该参数,将内部子组件的引用传递给委托,让
@ref绑定到真实子组件 - Dummy组件负责构建真实子组件并传递
@ref捕获逻辑,确保引用指向正确实例
内容的提问来源于stack exchange,提问作者GeorgeKarlinzer
相关产品推荐
相关产品推荐

