ASP.NET - Blazor - 从模板化组件返回所点击的泛型类型

4

我正在学习 Blazor 和 ASP.NET,并已经学习了6个月的 C#。

我制作了一个简单的模板组件:

@typeparam GenericType

<ul>
    @foreach (GenericType item in Items)
    { 
        <li @onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
    }    
</ul>

@code {
    [Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }

    [Parameter] public IReadOnlyList<GenericType> Items { get; set; }

    public void ItemClicked(GenericType item)
    {
         //To figure out...
    }
}

我正在页面组件中使用它:

<TestComponent GenericType="Thing" Items="ListOfThings">
    <FragmentToRender>
        <p>@context.Field</p>
    </FragmentToRender>
</TestComponent>

@code
{
    private List<Thing> ListOfThings =
        new List<Thing> {
            new Thing("Test"),
            new Thing("Test2")
        };

    public class Thing
    {
        public readonly string Field;

        public Thing(string field) => Field = field;
    }    
}
  • 元素在组件中的 OnClick 事件被触发时,我如何将特定实例的项目传回页面组件(即使得不同组件可以对所单击的项目进行某些操作,例如将其数据上传到其他地方)?
  • 非常感谢
  • 2个回答

    4
    你应该使用一个 EventCallback 来传递数据。
    @typeparam GenericType
    
    <ul>
        @foreach (GenericType item in Items)
        { 
            <li @onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
        }    
    </ul>
    
    @code {
        [Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
    
        [Parameter] public IReadOnlyList<GenericType> Items { get; set; }
    
        // Added EventCallback parameter
        [Parameter] public EventCallback<GenericType> OnClick { get; set; }
    
        public void ItemClicked(GenericType item)
        {
             // Checking if EventCallback is set
             if(OnClick.HasDelegate)
             {
                 // Calling EventCallback
                 OnClick.InvokeAsync(item);
             }
        }
    }
    

    然后只需将参数 OnClick 传递给该组件即可获取该项。

    @* Passing the OnClick parameter *@
    <TestComponent GenericType="Thing" Items="ListOfThings" OnClick="@HandleClick">
        <FragmentToRender>
            <p>@context.Field</p>
        </FragmentToRender>
    </TestComponent>
    
    @code
    {
    
        private void HandleClick(Thing item)
        {
            // Do what you want with the item
        }
    
        private List<Thing> ListOfThings =
            new List<Thing> {
                new Thing("Test"),
                new Thing("Test2")
            };
    
        public class Thing
        {
            public readonly string Field;
    
            public Thing(string field) => Field = field;
        }    
    }
    

    2
    注意:我已经对你的代码示例进行了一些修改... 需要注意的是:
    • 在FragmentToRender(item)之前加上@符号。这告诉编译器将FragmentToRender(item)作为可执行代码处理。否则,它将用作li元素的内容。

    • 在第二个版本的li元素中,我们将事件回调放在lambda表达式的主体中。如果您使用这个版本,请注释掉ItemClicked方法。

    TemplatedComponent.razor

     @typeparam GenericType
    
    <ul>
        @foreach (GenericType item in Items)
        {
            <li @onclick="() => ItemClicked(item)">@FragmentToRender(item)</li>
            @*<li @onclick="@(() => SelectedItem.InvokeAsync(item))">@FragmentToRender(item)</li>*@
        }
    </ul>
    
    @code {
    [Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
    
    [Parameter] public IReadOnlyList<GenericType> Items { get; set; }
    // Define an event call back property.
    [Parameter] public EventCallback<GenericType>  SelectedItem { get; set; }
    
    public async Task ItemClicked(GenericType item)
    {
        // Check if the event call back property contains a delegate. It's 
        // important to understand that the EventCallback type is not a true 
        // delegate. It is actually a struct that may contain a delegate 
        if(SelectedItem.HasDelegate)
        {
          await  SelectedItem.InvokeAsync(item);
        }
    
    }
    }
    

    TestComponent.razor

    <TemplatedComponent GenericType="Thing" Items="ListOfThings" 
                                         SelectedItem="SelectedItem">
        <FragmentToRender>
            <p>@context.Field</p>
        </FragmentToRender>
    </TemplatedComponent>
    
    
    @code
    {
     // Define a method that will be called by the event call back 'delegate'. It
     // receives a single parameter from the calling delegate.
    private async Task SelectedItem(Thing item)
    {
        Console.WriteLine(item.Field);
        await Task.CompletedTask;
    }
    private List<Thing> ListOfThings =
        new List<Thing> {
            new Thing("Test"),
            new Thing("Test2")
            };
    
    public class Thing
    {
        public readonly string Field;
    
        public Thing(string field) => Field = field;
    }
    }
    

    Index.razor

     @page "/"
    
    <TestComponent/>
    

    希望这有所帮助...

    我猜EventCallback<T>就像在C#中使用事件和委托,其中事件类型是委托的包装器。在第二个<li>版本中,直接调用事件回调函数是否有原因,你不需要检查是否有任何分配给它的内容? - Nick
    在SelectedItem中,使用await Task.Complete的好处是什么?据我所知,它会在await语句之后并发地运行代码(从函数返回,以便其他代码可以继续),然后当它完成时,在await语句下面继续运行,以新线程运行。 - Nick
    不,没有不检查EventCallback类型是否有委托的理由。如果您愿意,可以添加此类检查。实际上,我仅在ItemClicked方法中使用检查以进行教育目的。这并不是关键的... 您可以不使用它,并且如果您在github上花费一些时间,您可能会注意到,大多数由blazor团队生成的代码都不应用此检查。 - enet
    在SelectedItem中,使用await Task.Complete有什么好处吗?实际上没有任何好处。我本可以写成“private void SelectedItem(Thing item)”。但是,这样做是为了教育目的...好处在于你能够理解发生了什么,并且在需要编写类似代码时不会失败。我在这里使用“await Task.CompletedTask”,因为我想使用异步任务... - enet

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接