英文:
CascadingValue is sometimes null in the inner component
问题
以下是您要翻译的部分:
这是我的MainLayout.razor文件
```csharp
<div style="height: 100%; overflow-y: hidden;">
<div style="margin: 0; height: 100%; overflow-y: hidden;">
<div style="display: flex; flex-direction: column; height: 100%; overflow-y: hidden;">
<div style="flex-grow: 1; width: 100%; overflow: auto;">
<div style="height: 100%; overflow: auto;">
<main>
<div class="my-main-scrollbar">
<CascadingValue Value=PopupMessageBox>
@Body
</CascadingValue>
</div>
</main>
</div>
</div>
</div>
</div>
</div>
<PopupMessageBox @ref="PopupMessageBox" />
以下是MainLayout.razor.cs文件的关键部分:
public partial class MainLayout
{
public PopupMessageBox PopupMessageBox { get; private set; } = default!;
//...
然后在我的内部组件中,我有:
[CascadingParameter]
private PopupMessageBox PopupMessageBox { get; set; } = default!;
在OnInitializedAsync()
开始时,这通常很少为null。但如果我通过浏览器重新加载页面,它会为null。然而,到OnAfterRenderAsync()
触发时,它不为null。
有答案这里和这里,但对我来说不太清晰。似乎这是作为Task<something>
传递的,我需要等待它。这是有道理的。但是如何等待一个对象呢?我尝试的所有方法都无法编译。
希望这对您有所帮助。
<details>
<summary>英文:</summary>
Here's my MainLayout.razor
```csharp
<div style="height: 100%; overflow-y: hidden;">
<div style="margin: 0; height: 100%; overflow-y: hidden;">
<div style="display: flex; flex-direction: column; height: 100%; overflow-y: hidden;">
<div style="flex-grow: 1; width: 100%; overflow: auto;">
<div style="height: 100%; overflow: auto;">
<main>
<div class="my-main-scrollbar">
<CascadingValue Value=PopupMessageBox>
@Body
</CascadingValue>
</div>
</main>
</div>
</div>
</div>
</div>
</div>
<PopupMessageBox @ref="PopupMessageBox" />
And here's the key part of MainLayout.razor.cs
public partial class MainLayout
{
public PopupMessageBox PopupMessageBox { get; private set; } = default!;
//...
Then in my inner component I have
[CascadingParameter]
private PopupMessageBox PopupMessageBox { get; set; } = default!;
And this is rarely null at the start of OnInitializedAsync()
. But if I then reload the page via the browser - it's null. It however is not null by the time OnAfterRenderAsync()
fires.
There are answers here and here but they don't make sense to me. It seems to be that this is passed as a Task<something>
and I need to await it. Which makes sense. But hwo do I do that for an object? Everything I tried for that would not compile.
答案1
得分: 1
@HH已经找到了原因。问题在于您无法控制渲染过程以及何时渲染。在您的代码中,您经常级联使用`default!`,这是`null`。
有几种编码方法。HH在他的答案中建议了一种。
这是一个非常基本的演示:
一个包含弹出代码并级联自身的演示组件。
```csharp
<CascadingValue Value="this" IsFixed>
@ChildContent
</CascadingValue>
@*在此处放置弹出代码*@
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
// 打开/关闭弹出窗口的代码
}
MainLayout
<article class="content px-4">
<Component1>
@Body
</Component1>
</article>
以及一个演示Index
页面,以显示在级联时它从不为null:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
欢迎来到您的新应用程序。
<SurveyPrompt Title="Blazor对您的工作情况如何?" />
@code {
[CascadingParameter] private Component1 PopUp { get; set; } = default!;
protected override Task OnInitializedAsync()
{
ArgumentNullException.ThrowIfNull(PopUp);
return base.OnInitializedAsync();
}
}
然而,我对级联组件有一些疑虑:我认为通常来说这不是一个好的做法。您传递了一个具有许多功能(和负担)的对象,但在上下文中并不相关(这是一种代码异味!)。我更喜欢将上下文/状态与组件分离开来,并级联一个上下文对象,如下所示:
public class PopUpContext
{
public Func<Task<bool>> ShowAsync { get; private set; }
public bool IsDisplayed { get; private set; }
public PopUpContext(Func<Task<bool>> showAsync)
=> ShowAsync = showAsync;
public void SetAsDisplayed(bool value)
=> this.IsDisplayed = value;
}
我的PopUp
演示组件设置并级联上下文。
@if (_popUpContext.IsDisplayed)
{
<h3>Component1</h3>
<button class="btn btn-primary" @onclick=Close>Close</button>
}
<CascadingValue Value=_popUpContext IsFixed=true>
@ChildContent
</CascadingValue>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
private TaskCompletionSource<bool> _taskCompletionSource = new();
private PopUpContext _popUpContext;
public PopUp()
=> _popUpContext = new PopUpContext(this.ShowAsync);
public Task<bool> ShowAsync()
{
_popUpContext.SetAsDisplayed(true);
StateHasChanged();
_taskCompletionSource = new();
return _taskCompletionSource.Task;
}
public void Close()
{
_popUpContext.SetAsDisplayed(false);
StateHasChanged();
_taskCompletionSource.SetResult(true);
}
}
以及我的快速演示页面:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
欢迎来到您的新应用程序。
<SurveyPrompt Title="Blazor对您的工作情况如何?" />
<div class="m-2">
<button class="btn btn-dark" @onclick=Show>Open</button>
</div>
@code {
[CascadingParameter] private PopUpContext _popUpContext { get; set; } = default!;
protected override Task OnInitializedAsync()
{
ArgumentNullException.ThrowIfNull(_popUpContext);
return Task.CompletedTask;
}
private async Task Show()
=> await _popUpContext.ShowAsync.Invoke();
}
英文:
@HH has already identified the cause. The problem is your not in control of the render process, and what gets rendered when. In your code you're often cascading default!
which is null
.
There are several ways to code this. HH has suggested one in his answer.
Here's a very basic demo:
A demo component that contains the popup code and cascades itself.
<CascadingValue Value="this" IsFixed>
@ChildContent
</CascadingValue>
@*Put you popup code here*@
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
// Code to open/close the popup
}
MainLayout
<article class="content px-4">
<Component1>
@Body
</Component1>
</article>
And a demo Index
to show it's never null at cascade time:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
[CascadingParameter] private Component1 PopUp { get; set; } = default!;
protected override Task OnInitializedAsync()
{
ArgumentNullException.ThrowIfNull(PopUp);
return base.OnInitializedAsync();
}
}
However, I have an issue with cascading components: I don't think it's good practice in general. Your passing an object with a lot of functionality (and baggage) that isn't relevant in the context (it's a code smell!). I prefer to split out the context/state from the component, and cascade a context object like this:
public class PopUpContext
{
public Func<Task<bool>> ShowAsync { get; private set; }
public bool IsDisplayed { get; private set; }
public PopUpContext(Func<Task<bool>> showAsync)
=> ShowAsync = showAsync;
public void SetAsDisplayed(bool value)
=> this.IsDisplayed = value;
}
My PopUp
Demo component setting up and cascading the context.
@if (_popUpContext.IsDisplayed)
{
<h3>Component1</h3>
<button class="btn btn-primary" @onclick=Close>Close</button>
}
<CascadingValue Value=_popUpContext IsFixed=true>
@ChildContent
</CascadingValue>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
private TaskCompletionSource<bool> _taskCompletionSource = new();
private PopUpContext _popUpContext;
public PopUp()
=> _popUpContext = new PopUpContext(this.ShowAsync);
public Task<bool> ShowAsync()
{
_popUpContext.SetAsDisplayed(true);
StateHasChanged();
_taskCompletionSource = new();
return _taskCompletionSource.Task;
}
public void Close()
{
_popUpContext.SetAsDisplayed(false);
StateHasChanged();
_taskCompletionSource.SetResult(true);
}
}
And my quick Demo page:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class="m-2">
<button class="btn btn-dark" @onclick=Show>Open</button>
</div>
@code {
[CascadingParameter] private PopUpContext _popUpContext { get; set; } = default!;
protected override Task OnInitializedAsync()
{
ArgumentNullException.ThrowIfNull(_popUpContext);
return Task.CompletedTask;
}
private async Task Show()
=> await _popUpContext.ShowAsync.Invoke();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论