我有几个组件想要在我的Blazor应用程序中共享。这些恰好是SyncFusion组件——一个是SfToast,一个是SfDialog。我认为一个简单的方法是将组件放在MainLayout.razor上,然后对每个组件使用<CascadingValue>
将引用传递给所有子页面和组件。
只要通过<NavLink>
元素或使用NavigationManager.NavigateTo()
导航到页面,这就可以正常工作。但是,如果页面被深度链接或刷新,则最外层的<CascadingValue>
将变为null。为了解决这个问题,我创建了一个额外的伪<CascadingValue>
作为最外层,它确保我真正关心的值在刷新或直接链接上填充,但这感觉像是一个破解。我想知道我这样做的方式是否存在固有的问题,导致最外层的<CascadingValue>
在刷新时变为空。
下面是一些示例代码来说明这个问题。这是非常人为的,但这是我唯一能找到的方法,可以创建一个最小的可重复的例子来显示这个问题。
如果您运行该项目并单击";转到示例页";按钮,您将看到CompOne和CompTwo组件引用都已按预期设置为值。但是,如果刷新页面,您将看到CompOne引用(最外层的<CascadingValue>
(现在为null。
项目的布局如下(根据默认的Blazor Server模板创建,所以我只显示了我进行修改的区域(:
+BlazorSample|+组件(我添加了此文件夹(|-ComponentOne.razor|-ComponentTwo.剃须刀|+页|-索引.razor|-SamplePage.razor|+共享|-MainLayout.rarzor
MainLayout.rrazor
@inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
<CascadingValue Name="CompOne" Value="ComponentOne">
<CascadingValue Name="CompTwo" Value="ComponentTwo">
@Body
</CascadingValue>
</CascadingValue>
</div>
<BlazorSample.Components.ComponentOne @ref="ComponentOne"></BlazorSample.Components.ComponentOne>
<BlazorSample.Components.ComponentTwo @ref="ComponentTwo"></BlazorSample.Components.ComponentTwo>
</div>
@code{
public BlazorSample.Components.ComponentOne ComponentOne;
public BlazorSample.Components.ComponentTwo ComponentTwo;
}
ComponentOne.rarzor
@code {
// this would normally contain something useful
public string ThisIsNotUseful = "This is just a sample";
}
组件二。剃须刀
@code {
// this would normally contain something useful
public string ThisIsNotUsefulEither = "This is just a sample";
}
指数剃刀
@page "/"
@inject NavigationManager NavigationManager
<button class="btn btn-primary"
@onclick="@(() => NavigationManager.NavigateTo("/samplepage"))">
Go to Sample Page
</button>
SamplePage.rarzor
@page "/samplepage"
<div class="row">
<div class="col-12">
CompOne is null: @(CompOne == null)
</div>
</div>
<div class="row">
<div class="col-12">
CompTwo is null: @(CompTwo == null)
</div>
</div>
@code{
[CascadingParameter(Name = "CompOne")]
public BlazorSample.Components.ComponentOne CompOne { get; set; }
[CascadingParameter(Name = "CompTwo")]
public BlazorSample.Components.ComponentTwo CompTwo { get; set; }
}
原因
正如引用的HTML元素在渲染后才会链接一样,引用的组件也不会链接。这是因为组件是作为BuildRenderTree的一部分创建的,在这一点上引用被链接起来,就像这样
<SurveyPrompt @ref=MySurveyPrompt Title="How is Blazor working for you?" />
@code
{
SurveyPrompt MySurveyPrompt;
}
将转换为以下C#(在objDebugnet5.0RazorPages
中查找(
__builder.OpenComponent<BlazorApp58.Shared.SurveyPrompt>(1);
__builder.AddAttribute(2, "Title", "How is Blazor working for you?");
// This is the line that captures the reference
__builder.AddComponentReferenceCapture(3, (__value) => {
MySurveyPrompt = (BlazorApp58.Shared.SurveyPrompt)__value;
});
__builder.CloseComponent();
因此,第一次渲染(即直接导航(时,在渲染过程中(即渲染CascadingValue
时(没有引用,但当您单击按钮或其他东西导致NavigationManager.NavigateTo
时,Blazor将自动重新渲染以检查更改。此时,您现在有了要渲染的引用。
解决方案
- 首先,在布局中添加字段或属性
bool HasRendered
- 然后在UI中,确保第一次渲染(在有引用之前(不会渲染除引用的组件之外的任何子内容
@if (HasRendered)
{
<CascadingValue Value=@ComponentOne>
<CascadingValue Value=@ComponentTwo>
... your markup here ...
</CascadingValue>
</CascadingValue>
}
<ComponentOne ...../>
<ComponentTwo ...../>
- 然后,在第一次渲染修复了组件引用之后,允许对使用者标记进行完全渲染,并告诉Blazor重新渲染
在OnAfterPaint
中执行以下
if (firstRender)
{
HasRendered = true;
StateHasChanged();
}
附加
因为CascadingValue组件的值在任何消费者使用它们时都不会更改,所以您还可以添加IsFixed=True
,这将提高渲染性能。
只是想在这里为其他人节省一些时间。如果你面临与上述相同的问题。我对位于OnAfterRenderAsync中的HasRendered没有太大的运气。相反,在以这种方式渲染之前,请尝试检查每个级联参数是否准备就绪。。。
''
<AuthorizeView Roles="User">
<Authorized>
@if (thing1 != null && thing2 != null)
{
<CascadingValue Value="@thing1" Name="thingOne">
<CascadingValue Value="@thing2" Name="thingTwo">
@Body
</CascadingValue>
</CascadingValue>
}
</Authorized>
<NotAuthorized>
<ReDirectComponent ReDirectTo="/Login" />
</NotAuthorized>
</AuthorizeView>
''
级联参数的执行是从下开始的,页面刷新时不考虑父组件实例,因此它为null。请查看以下链接以供参考。
父实例:Blazor组件:从子组件更新模型时刷新父实例