CascadingValue 在内部组件中有时为空。

huangapple go评论59阅读模式
英文:

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&#39;s my MainLayout.razor

```csharp
    &lt;div style=&quot;height: 100%; overflow-y: hidden;&quot;&gt;
    	&lt;div style=&quot;margin: 0; height: 100%; overflow-y: hidden;&quot;&gt;
    		&lt;div style=&quot;display: flex; flex-direction: column; height: 100%; overflow-y: hidden;&quot;&gt;
    			&lt;div style=&quot;flex-grow: 1; width: 100%; overflow: auto;&quot;&gt;
    				&lt;div style=&quot;height: 100%; overflow: auto;&quot;&gt;
    					&lt;main&gt;
    						&lt;div class=&quot;my-main-scrollbar&quot;&gt;
    							&lt;CascadingValue Value=PopupMessageBox&gt;
    								@Body
    							&lt;/CascadingValue&gt;
    						&lt;/div&gt;
    					&lt;/main&gt;
    				&lt;/div&gt;
    			&lt;/div&gt;
    		&lt;/div&gt;
    	&lt;/div&gt;
    &lt;/div&gt;

&lt;PopupMessageBox @ref=&quot;PopupMessageBox&quot; /&gt;

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&lt;something&gt; 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
&lt;CascadingValue Value=&quot;this&quot; IsFixed&gt;
    @ChildContent
&lt;/CascadingValue&gt;

@*在此处放置弹出代码*@

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }

    // 打开/关闭弹出窗口的代码
}

MainLayout

        &lt;article class=&quot;content px-4&quot;&gt;
            &lt;Component1&gt;
                @Body
            &lt;/Component1&gt;
        &lt;/article&gt;

以及一个演示Index页面,以显示在级联时它从不为null:

@page &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

欢迎来到您的新应用程序。

&lt;SurveyPrompt Title=&quot;Blazor对您的工作情况如何&quot; /&gt;

@code {
    [CascadingParameter] private Component1 PopUp { get; set; } = default!;

    protected override Task OnInitializedAsync()
    {
        ArgumentNullException.ThrowIfNull(PopUp);
        return base.OnInitializedAsync();
    }
}

然而,我对级联组件有一些疑虑:我认为通常来说这不是一个好的做法。您传递了一个具有许多功能(和负担)的对象,但在上下文中并不相关(这是一种代码异味!)。我更喜欢将上下文/状态与组件分离开来,并级联一个上下文对象,如下所示:

public class PopUpContext
{
    public Func&lt;Task&lt;bool&gt;&gt; ShowAsync { get; private set; }

    public bool IsDisplayed { get; private set; }

    public PopUpContext(Func&lt;Task&lt;bool&gt;&gt; showAsync)
        =&gt; ShowAsync = showAsync;

    public void SetAsDisplayed(bool value)
        =&gt; this.IsDisplayed = value;
}

我的PopUp演示组件设置并级联上下文。

@if (_popUpContext.IsDisplayed)
{
    &lt;h3&gt;Component1&lt;/h3&gt;
    &lt;button class=&quot;btn btn-primary&quot; @onclick=Close&gt;Close&lt;/button&gt;
}

&lt;CascadingValue Value=_popUpContext IsFixed=true&gt;
    @ChildContent
&lt;/CascadingValue&gt;

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }
    private TaskCompletionSource&lt;bool&gt; _taskCompletionSource = new();
    private PopUpContext _popUpContext;

    public PopUp()
        =&gt; _popUpContext = new PopUpContext(this.ShowAsync);

    public Task&lt;bool&gt; ShowAsync()
    {
        _popUpContext.SetAsDisplayed(true);
        StateHasChanged();
        _taskCompletionSource = new();
        return _taskCompletionSource.Task;
    }

    public void Close()
    {
        _popUpContext.SetAsDisplayed(false);
        StateHasChanged();
        _taskCompletionSource.SetResult(true);
    }
}

以及我的快速演示页面:

@page &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

欢迎来到您的新应用程序。

&lt;SurveyPrompt Title=&quot;Blazor对您的工作情况如何&quot; /&gt;

&lt;div class=&quot;m-2&quot;&gt;
    &lt;button class=&quot;btn btn-dark&quot; @onclick=Show&gt;Open&lt;/button&gt;
&lt;/div&gt;

@code {
    [CascadingParameter] private PopUpContext _popUpContext { get; set; } = default!;

    protected override Task OnInitializedAsync()
    {
        ArgumentNullException.ThrowIfNull(_popUpContext);
        return Task.CompletedTask;
    }

    private async Task Show()
        =&gt; 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.

&lt;CascadingValue Value=&quot;this&quot; IsFixed&gt;
    @ChildContent
&lt;/CascadingValue&gt;

@*Put you popup code here*@

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }

    // Code to open/close the popup
}

MainLayout

        &lt;article class=&quot;content px-4&quot;&gt;
            &lt;Component1&gt;
                @Body
            &lt;/Component1&gt;
        &lt;/article&gt;

And a demo Index to show it's never null at cascade time:

@page &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

Welcome to your new app.

&lt;SurveyPrompt Title=&quot;How is Blazor working for you?&quot; /&gt;

@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&lt;Task&lt;bool&gt;&gt; ShowAsync { get; private set; }

    public bool IsDisplayed { get; private set; }

    public PopUpContext(Func&lt;Task&lt;bool&gt;&gt; showAsync)
        =&gt; ShowAsync = showAsync;

    public void SetAsDisplayed(bool value)
        =&gt; this.IsDisplayed = value;
}

My PopUp Demo component setting up and cascading the context.

@if (_popUpContext.IsDisplayed)
{
    &lt;h3&gt;Component1&lt;/h3&gt;
    &lt;button class=&quot;btn btn-primary&quot; @onclick=Close&gt;Close&lt;/button&gt;
}

&lt;CascadingValue Value=_popUpContext IsFixed=true&gt;
    @ChildContent
&lt;/CascadingValue&gt;

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }
    private TaskCompletionSource&lt;bool&gt; _taskCompletionSource = new();
    private PopUpContext _popUpContext;

    public PopUp()
        =&gt; _popUpContext = new PopUpContext(this.ShowAsync);

    public Task&lt;bool&gt; 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 &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

Welcome to your new app.

&lt;SurveyPrompt Title=&quot;How is Blazor working for you?&quot; /&gt;

&lt;div class=&quot;m-2&quot;&gt;
    &lt;button class=&quot;btn btn-dark&quot; @onclick=Show&gt;Open&lt;/button&gt;
&lt;/div&gt;

@code {
    [CascadingParameter] private PopUpContext _popUpContext { get; set; } = default!;

    protected override Task OnInitializedAsync()
    {
        ArgumentNullException.ThrowIfNull(_popUpContext);
        return Task.CompletedTask;
    }

    private async Task Show()
        =&gt; await _popUpContext.ShowAsync.Invoke();
}

huangapple
  • 本文由 发表于 2023年6月22日 07:51:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76527814.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定