通过JavaScript渲染Blazor组件时,如何传递CascadingParameter且不通过JS传值
问题
想给Counter组件传递CascadingParameter,但不通过路由,而是用JavaScript将组件渲染到Winbox.js窗口中。目前遇到的问题:
- 通过JS渲染时,CascadingParameter被初始化为null,触发NullReferenceException
- 通过路由访问Counter页面时,参数能正常加载,currentCount初始值为5
要求不通过JavaScript传递CascadingParameter值解决,因为存在调用组件时参数为null的场景(如MudDialog),或多个CascadingParameter的情况。
相关代码
1. Program.cs(注册Counter供JS调用)
builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
2. MainLayout.razor(定义CascadingValue)
<!-- 省略部分代码 --> <CascadingValue Value="_TestCascadingParameter"> <article class="content px-4"> @Body </article> </CascadingValue> <!-- 省略部分代码 --> @code{ private TestCascadingParameter _TestCascadingParameter = new() { GlobalCounter = 5 }; public class TestCascadingParameter { public int GlobalCounter { get; set; } } }
3. Counter.razor(目标组件)
@page "/counter" @using static BlazorApp1.Shared.MainLayout <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { [CascadingParameter] public TestCascadingParameter TestCascadingParameter { get; set; } private int currentCount = 0; protected override void OnParametersSet() { currentCount = TestCascadingParameter.GlobalCounter; } private void IncrementCount() { currentCount++; } }
4. WinBox.razor(触发Winbox的页面)
@page "/WinBox" @layout MainLayout @inject IJSRuntime _Js <PageTitle>Win box</PageTitle> <button @onclick="@OpenWinAsync">Open window</button> @code{ async Task OpenWinAsync() { await _Js.InvokeVoidAsync("MyWinBox.OpenTestWindow"); } }
5. MyWinBox.js(Winbox操作逻辑)
window.MyWinBox = { OpenTestWindow: async () => { let containerElement = new WinBox("WinBox.js"); await Blazor.rootComponents.add(containerElement.body, 'counter', {}); } };
解决方案
方案一:依赖注入共享参数实例
通过将CascadingParameter的实例注册为服务,让组件既可以通过组件树的CascadingValue获取,也可以通过依赖注入获取,兼容两种渲染场景。
修改步骤:
- Program.cs:注册参数类为单例服务
// 注册TestCascadingParameter为单例,确保全局唯一实例 builder.Services.AddSingleton<BlazorApp1.Shared.MainLayout.TestCascadingParameter>( new BlazorApp1.Shared.MainLayout.TestCascadingParameter { GlobalCounter = 5 } );
- MainLayout.razor:从服务注入实例,替代本地创建
<!-- 省略部分代码 --> <CascadingValue Value="_testCascadingParameter"> <article class="content px-4"> @Body </article> </CascadingValue> <!-- 省略部分代码 --> @code{ // 从DI注入实例 [Inject] private TestCascadingParameter _testCascadingParameter { get; set; } public class TestCascadingParameter { public int GlobalCounter { get; set; } } }
- Counter.razor:优先使用CascadingParameter,无则用注入的实例
@page "/counter" @using static BlazorApp1.Shared.MainLayout // 注入参数实例 @inject TestCascadingParameter InjectedTestParameter <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { [CascadingParameter] public TestCascadingParameter TestCascadingParameter { get; set; } private int currentCount = 0; private TestCascadingParameter _activeParameter; protected override void OnParametersSet() { // 优先取CascadingParameter,没有则用DI注入的实例 _activeParameter = TestCascadingParameter ?? InjectedTestParameter; currentCount = _activeParameter.GlobalCounter; } private void IncrementCount() { currentCount++; } }
方案二:在Blazor组件树内渲染后移至Winbox
让Counter始终处于MainLayout的CascadingValue范围内渲染,再将其DOM元素移动到Winbox窗口中,避免脱离组件树导致的参数丢失。
修改步骤:
- WinBox.razor:添加隐藏容器渲染Counter,再调用JS移动到Winbox
@page "/WinBox" @layout MainLayout @inject IJSRuntime _Js <PageTitle>Win box</PageTitle> <button @onclick="@OpenWinAsync">Open window</button> <!-- 隐藏容器,用来渲染Counter组件 --> <div @ref="_counterContainer" style="display: none;"> <Counter /> </div> @code{ private ElementReference _counterContainer; async Task OpenWinAsync() { // 等待Blazor完成Counter的渲染 await InvokeAsync(StateHasChanged); await Task.Delay(10); // 简单等待,生产环境可使用更可靠的渲染完成检测 // 调用JS将Counter移动到Winbox await _Js.InvokeVoidAsync("MyWinBox.MoveCounterToWinBox", _counterContainer); } }
- MyWinBox.js:修改逻辑为移动DOM元素,而非直接添加根组件
window.MyWinBox = { MoveCounterToWinBox: async (containerElement) => { const winbox = new WinBox("WinBox.js"); // 获取Counter的DOM元素并移动到Winbox内 const counterElement = containerElement.firstElementChild; winbox.body.appendChild(counterElement); // 可选:监听Winbox关闭事件,清理组件 winbox.onclose = () => { // 这里可以调用Blazor的组件销毁逻辑,避免内存泄漏 counterElement.remove(); return true; }; } };
内容的提问来源于stack exchange,提问作者TameemHabash
相关产品推荐
相关产品推荐

