最外面的 CascadingValue 在页面刷新或直接链接时丢失

Lex*_*Lex 2 syncfusion blazor blazor-server-side

我想在我的 Blazor 应用程序中共享几个组件。这些恰好是 SyncFusion 组件 - 一个是 SfToast,一个是 SfDialog。我认为一个简单的方法是将组件放在 MainLayout.razor 上,然后使用<CascadingValue>每个组件将引用传递给所有子页面和组件。

只要通过<NavLink>元素或使用导航到页面就可以正常工作NavigationManager.NavigateTo()。但是,如果页面被深层链接或刷新,则最外层<CascadingValue>将变为空。为了解决这个问题,我创建了一个额外的虚拟对象<CascadingValue>作为最外层,以确保我真正关心的值填充在刷新或直接链接上,但这感觉就像一个黑客。我想知道我这样做的方式是否存在本质上的错误,导致最外层<CascadingValue>在刷新时变为空。

下面是一些示例代码来说明问题。这是非常做作的,但这是我能找到的创建一个最小的可重现示例来显示问题的唯一方法。

如果运行该项目并单击“转到示例页面”按钮,您将看到 CompOne 和 CompTwo 组件引用均已按预期设置为值。但是,如果您随后刷新页面,您将看到 CompOne 引用(最外面的<CascadingValue>)现在为空。

项目的布局如下(从默认的 Blazor Server 模板创建,因此我只显示了我进行修改的区域):

+ Blazor 示例
| + 组件(我添加了这个文件夹)
  | - ComponentOne.razor
  | - ComponentTwo.razor
| + 页数
  | - 索引.剃刀
  | - SamplePage.razor
| + 共享
  | - MainLayout.razor

MainLayout.razor

@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;
}
Run Code Online (Sandbox Code Playgroud)

ComponentOne.razor

@code {
    // this would normally contain something useful
    public string ThisIsNotUseful = "This is just a sample";
}
Run Code Online (Sandbox Code Playgroud)

ComponentTwo.razor

@code {
    // this would normally contain something useful
    public string ThisIsNotUsefulEither = "This is just a sample";
}
Run Code Online (Sandbox Code Playgroud)

索引剃刀

@page "/"
@inject NavigationManager NavigationManager

<button class="btn btn-primary"
        @onclick="@(() => NavigationManager.NavigateTo("/samplepage"))">
    Go to Sample Page
</button>
Run Code Online (Sandbox Code Playgroud)

示例页面.razor

@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; }
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*ris 7

Cause

Just as referenced HTML elements are not linked until after a render, referenced components aren't either. This is because the component is created as part of BuildRenderTree, at which point the reference is linked up, like so.

<SurveyPrompt @ref=MySurveyPrompt Title="How is Blazor working for you?" />

@code

{
    SurveyPrompt MySurveyPrompt;
}
Run Code Online (Sandbox Code Playgroud)

Will transpile to the followint C# (look in obj\Debug\net5.0\Razor\Pages)

__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();
Run Code Online (Sandbox Code Playgroud)

So, the first time you render (i.e. direct navigation) you do not have the reference during the render (which is when CascadingValue is rendered) - but when you click a button or something to cause NavigationManager.NavigateTo Blazor will automatically re-render to check for changes. At this point, you now have references to be rendered.

Solution

  • First, in your layout add a field or property bool HasRendered.
  • Then in your UI, make sure the first render (before you have references) doesn't render any child content other than the components you are referencing.
@if (HasRendered)
{
  <CascadingValue Value=@ComponentOne>
    <CascadingValue Value=@ComponentTwo>
      ... your markup here ...
    </CascadingValue>
  </CascadingValue>
}
<ComponentOne ...../>
<ComponentTwo ...../>
Run Code Online (Sandbox Code Playgroud)
  • Then, after the first render has fixed up your component references, allow full rendering of the consumer markup and tell Blazor to re-render.

In OnAfterPaint do the following

if (firstRender)
{
  HasRendered = true;
  StateHasChanged();
}
Run Code Online (Sandbox Code Playgroud)

Additional

Because the values of the CascadingValue components won't change whilst any consumers are using them you can also add IsFixed=True, which will improve render performance.


归档时间:

查看次数:

1649 次

最近记录:

3 年,5 月 前