如何在父组件中渲染多个子组件而不在 Blazor 中调用 @ChildContent

Sim*_*mon 9 c# razor blazor blazor-client-side blazor-webassembly

在我们的 Blazor WebAssembly 应用程序中,我们有一个ParentComponent嵌套多个ChildComponents. 有3个文件:

  1. Test.razor是客户端,使用 ParentComponent 和 ClientComponent。
  2. ParentComponent.razor嵌套多个 ChildComponent。
  3. ChildComponent.razor是 ParentComponent 的子组件。

在此输入图像描述

文件:测试.razor

<ParentComponent DataSource="@AccountList">
    <ChildComponent TData="Account"><span>@context.FirstName</span></ChildComponent>
    <ChildComponent TData="Account"><i>@context.LastName</i></ChildComponent>
    <ChildComponent TData="Account"><b>@context.Age</b></ChildComponent>
</ParentComponent>

@code {

    // The datasource:
    private List<Account> AccountList = new List<Account>() {
        new Account() { FirstName = "Sam", LastName = "Soldier", Age = 33},
        new Account() { FirstName = "Lisa", LastName = "Johnson", Age = 25},
        new Account() { FirstName = "Jonas", LastName = "Peer", Age = 50 }
    };

    // A Simple Account-Class for the datasource
    class Account
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的文件按预期显示了以下数据:

结果

文件:ParentComponent.razor

@typeparam TData

<CascadingValue Value="this" Name="ParentComponent">
    @ChildContent
    <div class="parent-component">
        @foreach (var lData in DataSource)
        {
            @foreach (var lChild in m_Children)
            {
                <div class="child-component">
                    @lChild.ChildContent(lData);
                </div>
            }
        }

    </div>
</CascadingValue>

@code {

    [Parameter]
    public List<TData> DataSource { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    /// Collection of all added child components
    private List<ChildComponent<TData>> m_Children = new List<ChildComponent<TData>>();

    /// Add a child component (will be done by the child itself)
    public void AddChildComponent(ChildComponent<TData> pChildComponent)
    {
        m_Children.Add(pChildComponent);
        StateHasChanged();
    }
}
Run Code Online (Sandbox Code Playgroud)

文件:ChildComponent.razor

@typeparam TData

@code {

    /// Reference to the parent component
    [CascadingParameter(Name = "ParentComponent")]
    ParentComponent<TData> ParentComponent { get; set; }

    /// Child content of the component.
    [Parameter]
    public RenderFragment<TData> ChildContent { get; set; }

    // add ChildComponent to ParentComponent
    protected override void OnInitialized()
    {
        Console.WriteLine("ChildComponent.OnInitialized");
        ParentComponent.AddChildComponent(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:文件ParentComponent.razor包含语句 @ChildContent ,这在这里没有意义,因为我们想通过以下方式渲染子项的数据@lChild.ChildContent(lData)

// [...]
<CascadingValue Value="this" Name="ParentComponent">
@ChildContent // <- required, but want to remove, because here is othing to show
<div class="parent-component">
    @foreach (var lData in DataSource)
    {
        @foreach (var lChild in m_Children)
        {
            <div class="child-component">
                @lChild.ChildContent(lData); // We wender the child here
            </div>
        }
    }
// [...]
Run Code Online (Sandbox Code Playgroud)

当我们删除 时@ChildContent,就不会渲染任何内容。因此,@ChildContent似乎需要调用 来渲染子组件,但似乎没有必要,因为我们调用@lChild.ChildContent(lData);. 我们如何在没有语句的情况下呈现数据@ChildContent

也许 -block 中是否有任何@code可能与 相同的调用@ChildContent?例子:

ParentComponent.razor

@code {

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        if (firstRender)
        {
            ChildContent.Invoke(...); // ???
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Mis*_*goo 9

组件实例

组件是类,您需要先创建 Component 类的实例,然后才能访问其成员(例如其 ChildContent)。

Component 实例是在首次渲染时由 Blazor 创建的。

为了渲染子组件,它们需要成为 RenderTree 批处理的一部分。

如果“拥有”包含子组件的 RenderFragment (Parent.ChildContent) 的组件(父组件)本身不渲染该 ChildContent,则不会创建子组件实例。

像这样的一行代码正在访问子组件的实例属性,这要求子组件实例在被调用之前存在。

@lChild.ChildContent(lData); // We render the child here
Run Code Online (Sandbox Code Playgroud)

因此,简而言之,您需要在父组件中渲染 ChildContent 以创建子组件的实例并将它们注册到 RenderTree 中。

略有变化

如果您的子组件确实没有内部逻辑 - 正如您问题中的示例所示 - 那么它根本不需要是一个组件 - 它可以是一个 RenderFragment,这使得您将每个子组件包装在额外标记中的要求变得更加容易,因为 RenderFragment 不会像组件一样被实例化。

多子渲染片段

如果您编写 Parent 来接受多个子 RenderFragments,如下所示:

@typeparam TData

<div class="parent-component">
  @foreach (var lData in DataSource)
    {
        @foreach (var lChild in m_Children)
        {
            <div class="child-component">
                @lChild(lData);
            </div>
        }
    }

</div>

@code {
    [Parameter]
    public List<TData> DataSource { get; set; }

    [Parameter]
    public RenderFragment<TData> ChildFragment { set => m_Children.Add(value); }

    /// Collection of all added child fragments
    private List<RenderFragment<TData>> m_Children = new List<RenderFragment<TData>>();
}
Run Code Online (Sandbox Code Playgroud)

你可以这样使用它:

<ParentComponent DataSource="@AccountList">
    <ChildFragment><span>@context.FirstName</span></ChildFragment>
    <ChildFragment><i>@context.LastName</i></ChildFragment>
    <ChildFragment><b>@context.Age</b></ChildFragment>
</ParentComponent>
Run Code Online (Sandbox Code Playgroud)

作为额外的好处,您不再需要指定类型,TData因为它可以被推断。

** 注意:此技术仅适用于 RenderFragments,但您当然可以将任何您喜欢的内容放入 ChildFragment 中,包括子组件,并且仍然可以控制输出渲染。