Передайте дочерний компонент как параметр в Blazor

Я хотел бы сделать в Blazor то, что я обычно делаю в React: создать повторно используемый компонент, который внутренне использует другие дочерние компоненты, с возможностью передавать эти дочерние компоненты в качестве параметров. Мне это нужно, чтобы иметь возможность обрабатывать дочерние компоненты как зависимость, которую можно вводить по запросу с любой настраиваемой реализацией, которая может потребоваться в разных контекстах.

Представьте, например, TextBox.razor компонент, который дает вам возможность передать пользовательский компонент для рендеринга метки по вашему желанию, если он реализует ILabel интерфейс. Я пробовал что-то вроде этого, но синтаксис кажется неверным:

TextBox.razor

введите описание изображения здесь

Как видно из скриншота, Blazor не позволяет мне использовать параметр Label в качестве компонента. Есть идеи, как этого добиться?


person tocqueville    schedule 02.11.2019    source источник
comment
Вы пытаетесь передать экземпляр компонента или тип?   -  person Peter Morris    schedule 28.11.2019
comment
@PeterMorris экземпляр   -  person tocqueville    schedule 29.11.2019
comment
Будет ли рендер фрагментов делать то, что вы хотите?   -  person Peter Morris    schedule 30.11.2019
comment
Не совсем ... то, что я имел в виду, было своего рода инъекцией зависимостей компонентов. Я кодирую с использованием интерфейсов, а затем могу решить поменять реализацию.   -  person tocqueville    schedule 30.11.2019
comment
Нет возможности делать то, что ты хочешь. Вы не можете переместить существующий экземпляр в другую часть DOM.   -  person Peter Morris    schedule 30.11.2019
comment
Я открыл проблему в github: github.com/aspnet/AspNetCore/issues/17502   -  person tocqueville    schedule 02.12.2019
comment
Неужто можно было зарегистрировать абстрактную фабрику?   -  person Peter Morris    schedule 02.12.2019
comment
Да ... Как я в конце концов упомянул в выпуске на github, это возможно. Просто количество кода, необходимого для этого, делает всю операцию нелепой. Я предложил решение, но команда его отклонила.   -  person tocqueville    schedule 03.12.2019


Ответы (2)


Вы должны уметь делать это с помощью шаблонных компонентов.

Textbox.razor

@typeparam inputType
<div class="textbox">
    @if(LabelTemplate!=null && TItem!=null)
        @LabelTemplate(TItem)
    <input type="text"/>
</div>


    @code{
        [Parameter]
        public RenderFragment<inputType> LabelTemplate { get; set; }
        [Parameter]
        public inputType TItem { get; set; }
    }

В приведенном выше коде вы указываете, что компонент принимает тип с использованием @typeparam inputType и получает объект этого типа в качестве параметра TItem.

Вы также принимаете LabelTemplate, который принимает объект типа inputType. Чтобы отобразить этот фрагмент, мы вызываем @LabelTemplate и передаем наш параметр TItem.

Теперь давайте посмотрим, как использовать наш шаблонный компонент в новом компоненте под названием PersonForm.razor.

PersonForm.razor

<Textbox TItem="myPerson">
    <LabelTemplate>
        @context.Name
    </LabelTemplate>
</Textbox>
<Textbox TItem="myPerson">
    <LabelTemplate>
        @context.PhoneNumber
    </LabelTemplate>
</Textbox>

@code{

  Person myPerson = new Person { Name = "Jane Doe", PhoneNumber = "999 999 9999" };
    public class Person
    {
        public string Name { get; set; }
        public string PhoneNumber { get; set; }
    }
}

Я передаю свой объект Person в свойство TItem каждого компонента Textbox и получаю доступ к нему в LabelTemplate, используя синтаксис @context.

Сначала это может показаться запутанным, поэтому прочтите его здесь

Отредактировано. Это просто зависит от того, чего вы хотите достичь. С синтаксисом Verbose появляется гибкость со стороны «реализации» компонента. Вместо того, чтобы форсировать интерфейс, который может не работать с широким спектром моделей / классов, вы позволяете реализации указывать, что делать.

Если вы хотите что-то менее подробное / более жесткое, вы также можете сделать следующее.

@implements ILabel 
<div class="textbox"> 
    <label>@Text</label> 
    <input type="text"/> 
</div> 
@code
{ 
    [Parameter] 
    public string Text { get; set; } 
} 

ILabel.cs

    public interface ILabel
    {
        string Text { get; set; }
    }

person Eric Holland    schedule 02.11.2019
comment
Думаю, это способ добиться аналогичного результата, но это не совсем то, что я искал. Это кажется невероятно многословным по сравнению с простой передачей типа компонента в качестве параметра. - person tocqueville; 03.11.2019
comment
Возможно, я неправильно понял ваши первоначальные намерения. См. Отредактированную часть моего ответа - person Eric Holland; 04.11.2019

Я понимаю, что это, вероятно, поздно, но я просто боролся с этим и обнаружил, что это СУПЕР-просто! Подумал, что я поставлю простой ответ для людей, ищущих.

Вот мой файл OrdersNavigation.razor (который я хочу встроить в заголовок):

<div class="nav-strip">
    <NavLink href="orders">
        <Icon Name="@Icons.Cart" /> List
    </NavLink>
    <NavLink href="orders/create">
        <Icon Name="@Icons.Plus" /> Create
    </NavLink>
</div>

Вот мой PageHeader.razor:

<div class="page-header">
    <h3>@Title</h3>
    @Navigation
</h3>
<hr />

@code {
    [Parameter] public string Title { get; set; } = "[TITLE]";
    [Parameter] public RenderFragment Navigation { get; set; }
}

Обратите внимание, что свойство Navigation является RenderFragment - это ключ. Теперь на моей странице я могу просто добавить это так:

<PageHeader Title="Orders">
    <Navigation>
        <OrderNavigation />
    </Navigation>
</PageHeader>

Вы видите, что параметр Title вводится как обычно, но параметр Navigation вводится как элемент PageHeader! На самом деле, вы можете поместить что угодно в теги , и он будет отображаться там, где у вас есть @Navigation.

Ссылка: https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/

Сделал снимок на вашем примере:

Label.razor

<label>@Text</label>

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

TextBox.razor

<div class="textbox">
    <Label>
        <Text>
            <div>
                Embedded label <br />
                You can even drop components in here!
            </div>
        </Text>
    </Label>
    <input  />
</div>
person naspinski    schedule 17.10.2020