Aspnetcore: Компоненты на основе шаблонов

Созданный на 27 мар. 2018  ·  60Комментарии  ·  Источник: dotnet/aspnetcore

Например, вы должны иметь возможность создать компонент Grid или ListView, который привязывается к каждому элементу в коллекции, используя шаблон определения.

area-blazor

Самый полезный комментарий

Лично я считаю, что Razor следует как можно ближе придерживаться идиом HTML, а не дрейфовать к идиомам, которые мы видим в XAML. Это будет означать, что он будет больше похож на иерархию элементов, которую вы видите в <table> а не на типичные вещи XAML. Мы могли бы быть достаточно общими, чтобы поддержать любое из них, но я все еще твердо придерживаюсь идеи «Razor is HTML» прямо сейчас.

Все 60 Комментарий

В частности, это означает, что нам нужен какой-то способ сигнализировать о том, что данная часть дочернего контента в компоненте должна быть скомпилирована как Action<RenderTreeBuilder, T> для некоторого типа T, который мы должны иметь возможность динамически разрешать из использования при проектировании время. Тогда наш кодогенератор времени разработки сможет создать правильный контекст intellisense при написании выражений Razor внутри этого дочернего элемента.

Пример:

<MyListWithHeader RowItems=@People>
    <Title>
        Here I'm still in the outer context so can reference properties on the component itself 
        such as <strong i="8">@TitleText</strong>
    </Title>
    <RowTemplate> <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action<RenderTreeBuilder, Person> because we supplied
        an IEnumerable<Person> as the value for RowItems, so I can write @item.FirstName
    </RowTemplate>
</MyListWithHeader>

<strong i="9">@functions</strong>
{
    string TitleText { get; set; }
    IEnumerable<Person> People { get; set; }

    class Person
    {
        public string FirstName { get; set; }
    }
}

Я не уверен, насколько универсальным мы хотим сделать эту функцию. Возможно, менее общие, чем указано на рисунке выше. @rynowak был бы экспертом в том, что здесь разумно реализовать. Вся сложность во время проектирования / компиляции. Это не требует какой-либо поддержки во время выполнения, о которой я могу думать.

Привет, я попытался сделать это, передав IEnumerableв качестве параметра я смог создать сетку, но не смог обновить ее при изменении параметра. Это код:

<table class="@ClassName">
  <tr>
    <strong i="7">@foreach</strong> (var col in Columns)
    {
      <th>@col</th>
    }
  </tr>

  <strong i="8">@foreach</strong> (System.Data.DataRow row in table.Rows)
  {
    <tr>
      <strong i="9">@foreach</strong> (var col in Columns)
      {
        <td>@row[col]</td>
      }
      <strong i="10">@if</strong> (Select != null)
      {
        <td><a @onclick(() => Select(row))>Seleccionar</a></td>
      }
    </tr>
  }
</table>

@functions{
    public string ClassName { get; set; }
    public List<string> Columns { get; set; }
    public object DataSource { get; set; }
    private System.Data.DataTable table;


    private string Log;

    //call backs it seens I will have many of them

    public Action Change { get; set; }
    public Action<object> Select { get; set; }

    protected void UpdateState()
    {
      int count = ((IEnumerable<object>)DataSource).Count();
      Log = $"Table count rows:{table.Rows.Count} count object ${count}";
      StateHasChanged();
    }

    protected override void OnInit()
    {
      base.OnInit();

      Change += StateHasChanged;

      var enumerable = (IEnumerable<object>)DataSource;
      table = new System.Data.DataTable();

      if (Columns == null)
      {
        Columns = new List<string>();
        Columns.AddRange(enumerable.FirstOrDefault().GetType().GetProperties().Select(x => x.Name).ToList());
      }


      Columns.ForEach(x =>
      {
        table.Columns.Add(x);
      });

      enumerable.ToList()
                .ForEach(x =>
                {
                  var row = table.NewRow();

                  foreach (var property in x.GetType().GetProperties())
                  {
                    row[property.Name] = property.GetValue(x);
                  }

                  table.Rows.Add(row);
                });

      StateHasChanged();
    }
}

@SteveSandersonMS , являются ли Title и RowTemplate в вашем примере свойствами MyListWithHeader или специальных компонентов? Если это свойства, возможно, стоит подумать об альтернативных способах выражения этого ...

Я много работаю с XAML, и когда я начал создавать прототипы с Blazor, я быстро почувствовал потребность в этой функции. Как вы думаете, может ли быть разумным синтаксис элемента свойства XAML в этом сценарии? Хотя он довольно подробный, он ясно показывает, что мы назначаем здесь свойства, а не добавляем дочерние компоненты в MyListWithHeader . Пример:

<MyListWithHeader>
    <MyListWithHeader.Title>
       ...
    </MyListWithHeader.Title>
    <MyListWithHeader.RowTemplate>
       ...
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

Лично я считаю, что Razor следует как можно ближе придерживаться идиом HTML, а не дрейфовать к идиомам, которые мы видим в XAML. Это будет означать, что он будет больше похож на иерархию элементов, которую вы видите в <table> а не на типичные вещи XAML. Мы могли бы быть достаточно общими, чтобы поддержать любое из них, но я все еще твердо придерживаюсь идеи «Razor is HTML» прямо сейчас.

полностью в поддержку этого. если мы сможем сделать список «ObservableCollection», а не «IEnumerable», это будет бонусом. переход к xaml, например, pattrens, дает нам правильную систему шаблонов, которой не хватает html, все популярные фреймворки в веб-интерфейсе имеют систему шаблонов, что делает их очень расширяемыми, очевидно, что у blazor тоже должна быть одна, и вместо создания новой она было бы здорово иметь тот, который уже полюбился MS и многим разработчикам .Net, технически он по-прежнему будет основан на html / css, поэтому всех html-разработчиков по-прежнему будет привлекать технология, потому что это не будет xaml (не знаю, почему html guyx ненавидит xaml), но он будет следовать только за xaml pattrens.

@DamianEdwards Я полностью согласен с вами относительно того, что «Razor - это HTML». Однако значения атрибутов в HTML всегда указываются как атрибуты в открывающем теге. Нет способа (и нет необходимости) выражать сложные значения атрибутов, такие как шаблоны, в качестве дочерних элементов. Например, вы не можете писать

<a>
    <href>http://dot.net</href>
</a>

Поэтому я думаю, что технически то, к чему мы стремимся, просто не может быть выражено в чистом HTML.
Вот краткое изложение вариантов, которые я сейчас вижу:

  1. Придерживайтесь синтаксиса HTML : значения свойств можно указывать только в открывающем теге компонента.

    • Я думаю, что это то, что выбрал React, например:
      <MyListWithHeader RowTemplate={(data) => <p>...</p>} />
    • Плюс : очень близок к обычному синтаксису HTML.
    • Против : беспорядок для многострочных значений, т.е. более сложных шаблонов.
  2. Придерживайтесь синтаксиса HTML : в зависимости от контекста дочерние узлы компонента могут представлять вложенные элементы или присвоения свойств.

    • Плюс : очень близок к обычному синтаксису HTML.
    • Против : Может сбивать с толку, поскольку присвоения свойств и дочерние элементы синтаксически неразличимы. В этом примере <Title>...</Title> может быть автономным компонентом, а <RowTemplate>...</RowTemplate> может быть свойством (атрибутом) окружающего <MyListWithHeader> - без IntelliSense вы действительно не знаете
    • Против : может вызвать конфликты имен, если есть компонент и свойство с одинаковыми именами.
  3. Изобретайте новый синтаксис : <strong i="39">@functions</strong> { } или что-то совершенно другое.

    • Pro : позволяет создавать произвольно сложные шаблоны, оставаясь при этом читаемыми.
    • Против : может оттолкнуть веб-разработчиков

Вполне возможно, что я что-то неправильно понимаю. Если это так, пожалуйста, дайте мне знать.

@SvenEV Я не думаю, что синтаксис xaml кого-то заинтересует, если вы заметили, синтаксис шаблонов для angular, vue, react и миллиона других фреймворков, все имеют разные способы и разные синтаксисы для работы, но все они любимы, НЕТ? аналогично blazor может иметь свой собственный синтаксис, единственным профессионалом в этом случае будет то, что мы не будем изобретать новый синтаксис, просто перенося уже популярный синтаксис в этот фреймворк (следовательно, меньше времени на обучение для разработчиков xaml), но поскольку веб-разработчики считают, что они в любом случае будет изучать шаблоны blazor, независимо от того, следует ли Blazor xaml или нет, кривая обучения для веб-разработчиков остается прежней, единственный способ, которым веб-разработчики избегают этого недостатка, - это если blazor использует прямой html, который, я не думаю, будет таким же расширяемым, как шаблонная модель. :)

Таким образом, совершенно новый синтаксис шаблона дает каждому разработчику время для обучения. но синтаксис, подобный xaml, решает, по крайней мере, проблему половины разработчиков. но xaml также очень похож на html, в конце концов, оба являются разметкой. как только вы его изучите, это будет кусок пирога, хорошо, что путь к xaml в том, что вам вообще не нужно беспокоиться о css, все стили создаются с помощью прямых свойств на элементах управления. и даже стили компилирования также выполнены с тем же синтаксисом, но, с другой стороны, html и css - это совершенно разные языки с разным синтаксисом и структурой выполнения вещей.

Но зачем нам вообще шаблоны? Я имею в виду, когда вы можете делать foreach прямо здесь, в Razor. Мне это кажется намного проще, чем создание шаблонов.
Или это из-за повторного использования? Я могу представить себе отдельный компонент, который в этом случае выполняет foreach.

Шаблоны @MihaMarkic стандартизируют все, они увеличивают повторное использование и, что наиболее важно, скрывают весь сложный код, написанный для одного элемента управления, и вы можете просто использовать этот элемент управления с двумя строками кода и больше сосредоточиться на своей бизнес-логике, а не изобретать заново колесо.

Ну, я не знаю об этом. IMO foreach - это шаблон, просто более мощный, и вы можете поместить шаблон «foreach» в другой компонент и вызывать его из любого места с помощью этих двух строк кода.

foreach - это просто цикл, и да, для более простого элемента управления мы можем это сделать, например, для базового ListView (таблицы) мы можем просто создать элемент управления, который принимает некоторый ввод, выполняет его и выдает таблицу, а затем нам просто нужно поставить одну меткучто-то вроде этого, и тогда мы сможем создать таблицу, а также, если нам нужно настраиваемое представление списка, с очень современным дизайном, очень интерактивным и т. д., тогда мы можем создать другой компонентный элемент управления с помощью foreach, и много cutom css, и много кода Blazor в одном файле, может быть, в нескольких файлах, и оберните все это в один компонент, а затем снова используйте все это только с одним тегом. вы видите, к чему я клоню? независимо от того, сколько кода имеет элемент управления, все это можно обернуть в один тег :) @MihaMarkic

Речь идет не только о рендеринге нескольких элементов с использованием одного и того же шаблона. Также необходимо сделать несколько частей компонента настраиваемыми. В настоящее время в компоновке компонента может быть только одна такая «дыра», которую можно заполнить предопределенным свойством ChildContent , например:

<div style="some fancy styling">
    <strong i="7">@ChildContent</strong>
</div>

Теперь представьте, что вы хотите написать компонент с несколькими такими «дырами», как диалог, который позволяет настраивать содержимое, а также заголовок, или кнопку, где вы можете настраивать внутреннее содержимое, но также указывать шаблоны для контекстного меню и всплывающей подсказки. может быть. Так что было бы неплохо, если бы вы могли написать что-то вроде следующего (независимо от синтаксиса):

<MyButton>
    <h2>I am the button caption (the usual ChildContent)</h2>

    <MyButton.ContextMenu>
        <button>Context menu button 1</button>
        <button>Context menu button 2</button>
    </MyButton.ContextMenu>

    <MyButton.ToolTip>
        This explains <strong>what the button does</strong>. It is displayed when hovering over the button.
    </MyButton.ToolTip>
</MyButton>

@SvenEV Разве это не просто наличие нескольких отверстий и отсутствие строгого шаблона?

да, это хороший пример другого варианта использования, каждая часть должна быть настраиваемой.

@MihaMarkic Это правда, здесь мы говорим о двух разных вещах:

  1. Поддержка нескольких отверстий ( RenderFragment s / Action<RenderTreeBuilder> ), как в моем примере
  2. Поддержка шаблонов с вводом данных ( Action<RenderTreeBuilder, T> ), как в ListView и т. Д.

Я думаю, что первое является требованием для второго, и я надеюсь, что обе функции будут реализованы в какой-то момент. Это позволило бы нам создать несколько мощных библиотек компонентов.

Какую проблему это пытается решить? Мне просто интересно, так как я не видел ничего подобного в других фреймворках SPA.

Это противоречит структуре на основе компонентов? Кроме того, противоречит ли это обычным практикам веб-разработки, просто чтобы создать более «похожую на приложение» структуру?

@AWulkan это не противоречит нормальной структуре компонентов, но да, это немного похоже на структуру, подобную app-dev, но это не значит, что она уходит от веб-разработки, bcz все популярные фреймворки также делают то же самое, вся цель концепции СПА было это. SPA был создан, чтобы сделать веб-разработку больше похожей на разработку приложений. и теперь каждый фреймворк следует за спа. следующий шаг спа теперь pwa. Вы знаете, что такое pwa? он вводит некоторые основные функции из собственных приложений, такие как уведомления и автономная синхронизация, поэтому в конечном итоге веб-разработчик в целом пытается следить за разработкой собственных приложений.

@AWulkan Поскольку вы, кажется, знакомы с Vue, я просто заглянул в документацию Vue, чтобы увидеть, есть ли что-то подобное. Есть и это называется « Слоты », в частности «Именованные слоты» и «Слоты с ограничением» - именно об этом и идет речь. Это та же концепция. Может быть, это поможет вам разобраться в проблеме?

@SvenEV Ладно, тогда я вроде понимаю, что вы имеете в виду. Насколько я понимаю, это довольно нишевая функция. Спасибо за разъяснение.

Будет ли он использовать этот проект в качестве модели?
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md

Кажется, что слоты Vue.js похожи на шаблоны данных XAML.
Шаблоны предлагают мощный способ настройки существующих компонентов, один из случаев, который я видел, - это ListView, где по умолчанию без шаблонов он просто выполняет итерацию по списку строк и отображает его.

<ListView list=<strong i="7">@ListOfUrlsAsStrings</strong> />

С помощью шаблонов вы можете переопределить это, скажем, показать вместо этого список тегов изображений, используя переданный Type T .

<ListView list=@ListOfUrlsAsStrings>
    <ListView.ListTemplate>
         <img src=<strong i="12">@item</strong> alt="overriden by a template" />
        <CustomComponentHere />
    </ListView.ListTemplate>
</ListView>

Это значительно повысит возможность повторного использования компонентов, поскольку они могут быть динамическими. Сторонние поставщики компонентов могут проявить более творческий подход к созданию компонентов таким образом.

да именно с шаблоном данных привязка становится проще и заметнее

Мы очень надеемся, что компоненты Blazor получат хорошую поддержку для вложения и шаблонов.
Ранее у нас был ряд обсуждений с @Mehul , @NTaylorMullen , @DamianEdwards на ту же тему для помощников по тегам Razor, в которых мы пытались объяснить, почему нам нужен более похожий на XAML подход. Соответствующие проблемы с GitHub: aspnet / Razor # 474, aspnet / Razor # 576, aspnet / Razor # 570.

Мы разрабатываем компоненты пользовательского интерфейса, такие как сетки данных, и типичная разметка для настройки виджета может быть такой же сложной, как это:

<TabPanel>
    <Grid>
        <DataSource>
            <More about data source />
        </DataSource>

        <Column />
        <Column>
            <Lookup>
                <DataSource>
            . . .
            <CellTemplate>
                arbitrary html
            </CellTemplate>
        </Column>

        <DetailTemplate>
            <div>arbitrary content</div>
            <ThirdPartyComponent />

            <Grid>
                detail grid configuration with own templates (and there may be more levels)
            </Grid>
        </DetailTemplate>
    </Grid>
</TabPanel>

Для такой иерархии разработчик хотел бы иметь точное понимание контекста и интеллект.

@AlekseyMartynov можем ли мы иметь xaml-подобный стиль + анимацию в долгосрочной перспективе?

Я так рад, что эта идея набирает обороты. Поскольку MS впервые предложила идею создания шаблонных элементов управления с помощью Silverlight, WPF и UWP, имеет смысл сделать это частью Blazor.

Один из вопросов - это контексты данных / кода. Когда шаблон cshtml ссылается на код в привязанном объекте C #, который, вероятно, не будет находиться в том же контексте, что и более крупный компонент. Так разве не потребуется какое-то динамическое связывание данных? Или в шаблоне должно быть строгое требование TargetType, чтобы синтаксический анализатор знал, как разрешать ссылки на элементы объекта, привязанные к данным?

В этой заметке, как насчет динамической привязки данных и свойств зависимостей? Насколько я могу судить, сейчас ничто не может этому помешать. Или отражение будет проблематичным, если C # или IL будут скомпилированы в wasm, а не интерпретироваться с помощью моно?

На мой взгляд, реализация того, что предлагает @AlekseyMartynov (или его разновидностей), имеет решающее значение для успеха Blazor. На практике многие разработчики, пишущие корпоративные приложения, зависят от богатого набора простых в использовании сторонних компонентов, и я надеюсь, что этот проект очень скоро достигнет той точки, когда разные поставщики смогут начать разработку компонентов Blazor.

В моем проекте Xamzor я реализовал некоторую ограниченную поддержку шаблонов, например:

<ListView ItemTemplate="typeof(MyCustomTemplate)" ... />

где MyCustomTemplate - это еще один компонент Blazor со свойством DataContext, которое назначается ListView. Хотя это работает достаточно хорошо, все же желательно определять шаблоны встроенными, а не в отдельном компоненте, особенно если шаблоны небольшие и простые.

Возможно, контекстные компоненты ember.js могут послужить источником вдохновения: https://www.mutuallyhuman.com/blog/2016/09/23/creating-an-accordion-with-contextual-components-in-ember

@SvenEV Я согласен с тем, что в случае простых шаблонов встроенные шаблоны были бы полезны, но у нас все еще должна быть возможность для отдельных шаблонов компонентов, как вы реализовали в проекте ur, потому что это помогает нам в разделении проблем и повторном использовании шаблона :)

Три вещи в моей голове:

  1. Сделайте компоненты изначально иерархическими (например, свойство ParentComponentType; чтобы вы могли принудительно применять MyTableComponent -> MyRowComponent -> MyColumnComponent). Если ParentComponentType имеет значение NULL, компонент может быть дочерним по отношению к любому типу компонента (например, MyTableComponent.ParentComponentType может иметь значение NULL).

Иерархия обеспечивается с помощью объявлений в определениях компонентов, например:

`` С #
// В MyRowComponent
Введите ParentComponentType {get; } = typeof (MyTableComponent);
...

// In MyColumnComponent
Type ParentComponentType {get; } = typeof(MyRowComponent); 
...
Thus, MyColumnComponent can only be created within a MyRowComponent, and MyRowComponent can only be created within a MyTableComponent.

2. Allow components to be extended via ITemplateable interface. When the ITemplateable.Template [Tag] property is valued, it overrides the component's default rendering. Internally, it's something like:

```C#
    if (component is ITemplateable)
    {
        if (component.Template != null)
        {
            … return template rendering ...
        }
    }
    … return default rendering ...
  1. Создайте интерфейс ITemplate, который можно реализовать как необязательный базовый класс BlazorTemplate, который можно использовать для объективизации программирования с помощью шаблонов.

Пример использования, где MyColumnComponent является ITemplateable:

    <MyTableComponent>  
        <MyRowComponent>  
            <MyColumnComponent>  
                <Template>  
                    …  template codes here; can contain a call to another component ...
                </Template>  
            </MyColumnComponent>  
        </MyRowComponent>  
    </MyTableComponent>  

Если MyColumnComponent определен как ITemplateable, он поддерживает тег <Template> … </Template> . Записи в этом теге переопределяют рендеринг MyColumnComponent по умолчанию.

Внутри MyColumnComponent может иметь / использовать реализацию BlazorTemplate, чтобы позволить ему программно обрабатывать особые поведения, события и функции шаблона.

Я голосую за синтаксис элемента свойства

<Button>
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="1">First item</MenuItem>
      <MenuItem Header="2">Second item</MenuItem>
    </ContextMenu>
  </Button.ContextMenu>

  Right-click me!
</Button>

Как альтернатива

<Button ContextMenu=@GetContextMenuRenderFragment()>
  Right-click me!
</Button>

Мне это нравится, но разве веб-пуристы не задирают нос, считая, что это слишком похоже на XAML?

Нет, все будет в порядке, property element syntax - это альтернатива html attribute syntax (не замена). Но я удивляюсь, сколько возможностей он предоставляет разработчикам компонентов (разработчикам библиотеки элементов управления пользовательского интерфейса).

Мне это также нравится, поскольку он более четко передает, как дочерний элемент относится к родителю, это особенно хорошо для шаблонов, поскольку вы сможете отличить, что это шаблон данных от родителя, а не настраиваемый компонент, который не имеет отношения к родительский компонент.

Нравится:

<List>
    <List.DataTemplate>
      <!-- Template Here -->
    </List.DataTemplate>
    <ComponentWithNoRelationToTheList />
</List>

Это дает вам представление о том, что DataTemplate связан со списком, а не с настраиваемым шаблоном в целом. Хотя я согласен, как XAML, это должна быть просто альтернатива.

Что, если «Шаблон» - это зарезервированное / специальное ключевое слово для любого шаблонного компонента Blazor? При использовании в качестве тега он может содержать коды шаблонов. При использовании в качестве атрибута он может указывать именованный шаблон, который может содержать коды шаблонов.

Итак, представим себе такой компонент, как:

ComponentTemplateTest.cshtml

<strong i="7">@functions</strong>
{
  [Parameter]
  public RenderFragment Template { get; set; }

  [Parameter]
  public RenderFragment<int> TemplateWithInt { get; set; }

  [Parameter]
  public RenderFragment<TestObject> TemplateWithObject { get; set; }

  public class TestObject
  {
      public string PropString { get; set; }
      public int PropInt { get; set; }
  }

  private TestObject testobject = new TestObject();
}

<div>
    <div><button onclick="@((x) => this.testobject.PropInt++)">test</button></div>
    <div>@Template</div>
    <div>@((RenderFragment)(x => TemplateWithInt(x, testobject.PropInt)))</div>
    <div>@((RenderFragment)(x => TemplateWithObject(x, testobject)))</div>
</div>

мы можем использовать это как

<ComponentTemplateTest>
    <Template>
        test simple template
    </Template>

    <TemplateWithInt params="param1">
        <strong i="11">@param1</strong>
    </TemplateWithInt>

    <TemplateWithObject params="paramObject">
        @paramObject.PropInt
    </TemplateWithObject>
</ComponentTemplateTest>

нам нужен атрибут типа params потому что Blazor записывает код как

            builder.OpenComponent<StandaloneApp.ComponentTemplateTest>(16);
            builder.AddAttribute(17, "template", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
                builder2.AddContent(18, "\n        test simple template\n    ");
            }
            ));
            builder.AddAttribute(19, "templatewithint", (Microsoft.AspNetCore.Blazor.RenderFragment<System.Int32>)((builder2, param1) => {
                builder2.AddContent(20, param1);
            }
            ));
            builder.AddAttribute(21, "templatewithobject", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.ComponentTemplateTest.TestObject>)((builder2, paramObject) => {
                builder2.AddContent(22, "\n        ");
                builder2.AddContent(23, paramObject.PropInt);
                builder2.AddContent(24, "\n    ");
            }
            ));
            builder.CloseComponent();

этого будет достаточно? что-то пропало?

@uazo Обратите внимание, что без синтаксиса элемента свойства невозможно различить property и component .

@danieldegtyarev Я думаю, что мы говорим здесь о двух разных вещах ... моя цель пока только шаблон для компонентов ...

взгляните на https://github.com/uazo/Blazor/tree/dev-experimental-templated-component
вам нужно собрать и установить это расширение ...

«Шаблон», используемый в качестве тега, является декларативным и, естественно, встроенным:

<MyCom MyComProp="MyComPropValue">
    <Template>
        Components, @MyCom.MyComProp, HTML, content and <strong i="6">@values</strong>
    </Template>
</MyCom>

«Шаблон» используется в качестве тега для присвоения «именованного шаблона». Внутри именованный шаблон будет иметь доступ к своему родительскому компоненту, поэтому также возможно что-то вроде MyCom.MyComProp :

<MyCom MyComProp="MyComPropValue">
    <Template Name="MyTemplate {Content: @content; Values: @values}" />
</MyCom>

«Шаблон» используется в качестве атрибута для присвоения «именованного шаблона». Внутри именованный шаблон будет иметь доступ к своему родительскому компоненту, поэтому также возможно что-то вроде MyCom.MyComProp :

<MyCom 
    MyComProp="MyComPropValue"
    Template="MyTemplate {Content: @content; Values: @values}" />

«Шаблон», используемый либо как тег, либо как атрибут, должен привести к одной и той же компиляции, чтобы компонент отображал предоставленную информацию о шаблоне, которая просто переопределяет визуализацию компонента по умолчанию.

«Именованный шаблон» - это повторно используемый шаблон. Создание «именованного шаблона» почти похоже на создание компонента. Однако большая разница в том, что именованный шаблон можно использовать только через поддержку компонента «Шаблон».

Шаблон может содержать компоненты, но должны быть выполнены проверки, чтобы предотвратить потенциальные бесконечные циклы, когда, например, компонент, который использует шаблон, который использует тот же компонент, закодирован ... Возможно, ошибка разработки / времени компиляции?

Вопросы: Может ли именованный шаблон наследоваться от другого шаблона? Можно ли ограничить использование именованного шаблона только с определенными типами компонентов?

Я думаю, вы, ребята, должны как можно больше использовать синтаксис HTML, не вводя ничего нового. Нет синтаксиса Xamarin xaml или уродливых компонентов веб-форм asp.net (asp-повторитель и т. Д.).
Я из мира asp.net mvc, я помню радость и свободу, когда я переключился с «asp.net webforms» на «asp.net mvc», больше никаких странных компонентов, таких как asp: Repeater и так далее.
Пожалуйста, не позволяйте BLAZOR стать новой "веб-формой asp.net".

Я согласен с @GoranHalvarsson . Создание шаблонов в Blazor должно быть только реализацией универсального RenderFragment., с тем же вариантом использования (первый пример в сообщении @etmendz ).

Для этого я обновил свою ветку, теперь с полной поддержкой intellisense в Visual Studio (параметры и разрешенный дочерний элемент).

может кто-нибудь дать мне обратную связь?

@GoranHalvarsson Я согласен. MVP может быть для компонентов Blazor, чтобы иметь базовую поддержку шаблонов. В этом смысле «Шаблон» может содержать коды HTML, компонентов, содержимого и Razor. По сути, компоненты + шаблоны Blazor должны быть узнаваемыми кодами HTML + Razor. Декларативный синтаксис соответствует этому счету, который может быть, например, ключевым словом <Template> или @template .

Следующим этапом могут быть библиотеки API / расширяемости Blazor, которые позволяют создателям компонентов продвигать свой собственный бренд - например, синтаксис, подобный вспомогательному HTML (Progress Telerik / Kendo UI) или синтаксис, подобный Xamarin / XAML (ooui.wasm, Platform .Uno) вместо этого могут быть доставками сторонних поставщиков / поставщиков. В Blazor можно работать над программируемостью шаблонов и гибкостью компиляции в тесном сотрудничестве с партнерами-разработчиками компонентов для решения конкретных проблем / потребностей / требований.

В этой ветке есть предложение о повторно используемых шаблонах. Повторно используемые шаблоны или «именованные шаблоны» могут способствовать повторному использованию кода. Хотя, честно говоря, я не вижу в этом особой ценности и может иметь низкий приоритет. Скорее всего, если он предназначен для повторного использования, то, возможно, это должен быть компонент? ;-)

@uazo IMHO, основываясь на ваших примерах, передача params в <Template> похоже, нарушает декларативный характер простого использования параметра / значения непосредственно внутри тега <Template> .

Я также сомневаюсь в том, чтобы позволить компонентам поддерживать несколько свойств шаблона. В этом смысле сделать его слишком вольным. Я думаю, что у Blazor должен быть более простой и стандартный способ поддержки шаблонов компонентов, возможно, через интерфейс ITemplate и некоторые настройки компилятора здесь и там.

@SteveSandersonMS для https://github.com/aspnet/Blazor/issues/404#issuecomment -377229910
с кодом aspnet / AspNetCore # 15829 будет

MyListWithHeader.cshtml

<strong i="10">@functions</strong>
{
    [Parameter]
    public RenderFragment Title { get; set; }

// NOT MANDATORY
    [Parameter]
    public RenderFragment ChildContent { get; set; }
// *******************

    [Parameter]
    public IEnumerable<example.Person> RowItems { get; set; }

    [Parameter]
    public RenderFragment<example.Person> RowTemplate { get; set; }
}

<div>
    <strong i="11">@Title</strong>
</div>

<strong i="12">@foreach</strong> (var item in RowItems)
{
    <div>
    @RowTemplate.Render(item)
    </div>
}

<strong i="13">@ChildContent</strong>

example.cshtml

<strong i="17">@page</strong> "/component-test"

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        Here I'm still in the outer context so can reference properties on the component itself
        such as <strong i="18">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action RenderTreeBuilder, Person
        because we supplied
        an IEnumerable Person as the value for RowItems, so I can write <b>@item.FirstName</b>
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

<strong i="19">@functions</strong>
{
  string TitleText { get; set; }
  IEnumerable<Person> People { get; set; }

  protected override void OnInit()
  {
      var people = new List<Person>();
      people.Add(new Person { FirstName = "uno" });
      people.Add(new Person { FirstName = "due" });
      people.Add(new Person { FirstName = "tre" });

      this.People = people;
  }

  public class Person
  {
      public string FirstName { get; set; }
  }
}

с полной поддержкой intellisense!

@uazo Очень-очень хорошо! Есть только одно: удалим WithParams="item" и просто используем @item для доступа к «контексту» шаблона.

ммм .. а если item уже существует в компоненте example ?
Поскольку имя переменной, используемое для доступа к «контексту» шаблона, нельзя игнорировать, поэтому с помощью WithParams разработчики могут выбрать имя переменной.

вы думаете о проводном имени по умолчанию (например, ChildContent ) с необязательным WithParams или без него? Или синтаксис вроде <MyListWithHeader.RowTemplate WithParams=@item> ?

Оглядываясь назад, можно сказать, что имя по умолчанию не подходит для вложенных шаблонов ...

Давай подумаем. Шаблон - это генерация кода с одним параметром. Шаблон имеет доступ только к этому элементу. Так что если мы «зарезервируем» переменную item - это безопасно и не может конфликтовать. Вложенные шаблоны имеют свою собственную область видимости, и использование @item по-прежнему безопасно.

Кстати, похоже, что этот шаблон - scope должен иметь свой блок @functions

Давайте

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        ...<strong i="6">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        ... <b>@item.FirstName</b>

        <MyListWithHeader>
            <MyListWithHeader.RowTemplate WithParams="item">
                @item.FirstName
            </MyListWithHeader.RowTemplate>
        </MyListWithHeader>

    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

будет рендерер как

protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
{
    base.BuildRenderTree(builder);
    builder.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(0);
    builder.AddAttribute(1, "RowItems", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck<System.Collections.Generic.IEnumerable<StandaloneApp.Pages.testtemplated.example.Person>>(People));
    builder.AddAttribute(2, "Title", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
        builder2.AddContent(3, "\n        Here I\'m still in the outer context so can reference properties on the component itself\n        such as ");
        builder2.AddContent(4, TitleText);
        builder2.AddContent(5, "\n    ");
    }
    ));
    builder.AddAttribute(6, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder2, item) => {
        builder2.AddContent(7, "\n        ");
        builder2.AddContent(8, "\n        Here I\'m in the context of an Action RenderTreeBuilder, Person\n        because we supplied\n        an IEnumerable Person as the value for RowItems, so I can write ");
        builder2.OpenElement(9, "b");
        builder2.AddContent(10, item.FirstName);
        builder2.CloseElement();
        builder2.AddContent(11, "\n\n        ");
        builder2.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(12);
// line 43
        builder2.AddAttribute(13, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder3, item) => {
            builder3.AddContent(14, "\n                ");
            builder3.AddContent(15, item.FirstName);
            builder3.AddContent(16, "\n            ");
        }
        ));
        builder2.CloseComponent();
        builder2.AddContent(17, "\n\n    ");
    }
    ));
    builder.CloseComponent();
}

с участием

Error   CS0136  A local or parameter named 'item' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

StandaloneApp\obj\Debug\netstandard2.0\Pages\testtemplated\example.g.cs line 43

Да, конечно, вы должны решить это во время кодогенерации.
Но ... я думаю, что шаблон должен быть скомпилирован в component (возможно, в специальный компонент), а не в RenderFragment потому что нам нужно управлять его состоянием, например, чтобы вызвать StateHasChanged (), когда @item.FirstName мутирует.

Да, конечно, вы должны решить это во время кодогенерации.

Я не думаю, что это возможно.
Во время генерации кода (модифицированного бритвой) все узлы пересекаются, некоторые из них являются html, другие - узлами C #: те, у которых есть C #, будут неправильными, потому что переменные не будут определены, и, следовательно, тип переменной не будет известен (и нам нужно восстановить переменная типа T из RenderFragment<T> , потому что мы знаем T ) ... поэтому я не знаю, какое имя переменной использовать.
Возможно, вы могли бы поискать эту неопределенную переменную, но это кажется беспорядком

Но ... я думаю, что шаблон должен быть скомпилирован в компонент (возможно, специальный компонент), а не в RenderFragment, потому что нам нужно управлять его состоянием, например, чтобы вызвать StateHasChanged (), когда@item.FirstName мутирует.

Не думаю, что нужно что-то еще вводить.
Если вам нужен компонент, используйте BlazorComponent , если вам нужно «свойство шаблона», используйте RenderFragment<T>
В конце концов, StateHasChanged() для RenderFragment вызывается базовым компонентом StateHasChanged() , мне не нужно принудительно вызывать изменения каким-либо другим способом.

Извини, что вмешиваюсь, потому что это звучит как отличный разговор, но ...

Разве это не было бы решено с помощью концепции контекста данных, такой как WPF / UWP? Тогда в вашем шаблоне вы всегда будете ссылаться на this.DataContext.[Member] .

@ peter0302 Мне лично нравится эта идея ...

Задача остается строго типизированной. Конечно, это решается в XAML привязкой, выполняемой во время выполнения. Шаблон данных здесь, вероятно, должен поддерживать ввод с помощью универсальных шаблонов. Но определенно выполнимо.

С другой стороны, я бы также поддержал полностью динамический механизм привязки во время выполнения.

Вы можете как-то указать тип данных через объявление. В чем-то похоже на x:DataType в XAML, хотя предполагает, что он должен быть более идеальным и менее подверженным ошибкам.

@uazo По моему

1) То, что свойству типа RenderFragment присваивается квалифицированное имя свойства в теге, например <MyListWithHeader.Title /> мне нравится, но обработка нарушает мою прежнюю интуицию, что свойства с ParameterAttribute установлены через html-атрибуты. Как насчет того, чтобы иметь еще один атрибут C # для этих свойств, чтобы различать такое поведение? Нравится [ContentParameter] ?

2) Свойству типа RenderFragment<Person> присваивается тег свойства с атрибутами.
<MyListWithHeader.RowTemplate WithParams="item" /> показалось немного странным. Это не атрибут, который устанавливает свойство параметра, а то, какое имя переменной следует использовать в лямбда-выражении во время генерации кода, локального для этого шаблона. Здесь снова было бы хорошо иметь другой синтаксис, чтобы различать разницу. Но я не уверен, насколько творческим можно быть, чтобы соответствовать инструментам и компилятору. <MyListWithHeader.RowTemplate params(item) /> возможно?

3) Если я хочу показать <MyListWithHeader RowItems=<strong i="16">@PeopleOnline</strong> /> и <MyListWithHeader RowItems=<strong i="18">@PeopleOffline</strong> /> , нужно ли мне снова указывать шаблоны? Есть ли способ использовать их повторно? Я подумал, возможно, с помощью этого механизма можно выполнить некоторую инициализацию на основе модели, как в EditorFor, с разными селекторами свойств с использованием одних и тех же шаблонов.

Когда я новичок в фреймворке, обсуждать реализацию может быть немного рискованно, но ...

если вы используете отдельный атрибут параметра для назначения шаблонов, как описано в пункте 1 выше, вы можете добавить к нему логический флаг (по умолчанию true), чтобы указать, следует ли применять значение по умолчанию (если не указано явно). Затем вы можете решить пункт 3 выше, введя интерфейс, подобный IDefaultTemplateProvider, который можно использовать для предоставления определений для тегов свойств-шаблонов по отдельности. Затем они могут быть обнаружены с помощью отражения так же, как вы обнаруживаете IComponent (ы) и используете их в тех случаях, когда установлен флаг использования по умолчанию, а пользователь его не предоставляет. Вы можете разрешить несколько компонентов шаблона, реализующих интерфейс, но выдать однозначное исключение, если тег свойства будет обнаружен несколько раз. Таким образом, вы можете предоставить стандартизированные шаблоны для универсального компонента, специфичного для приложения, которые используются несколько раз и получить разрешение во время разработки, возможно?

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

Я хочу выполнить конфигурацию компонентов на основе модели с помощью метаданных и файлов ресурсов для получения статического переведенного текста, проверки и т.п. Я думал, что этот механизм шаблонов может помочь в этом. Чтобы оценить его, я создал интерфейс public interface IRenderFragment<T> { RenderFragment UsingDataItem(T item); } и сделал обычный компонент, который также реализует этот интерфейс, который будет использоваться в качестве шаблона. Затем сделал общий компонент, используя его следующим образом

  <InputField For=@DataSource(Model, x => x.NewTodo) Template="@InputBasic.Template" />

Таким образом, компонент InputField имеет информацию о том, как получить метаданные и т. Д., И может передать ее в модель, которая отображается в предоставленном вами шаблоне. Работает очень хорошо.

В XAML это единственный механизм для рендеринга вещей, когда в компоненте реализовано окружающее поведение, например, в представлении списка, тогда вам нужно предоставить шаблон элемента. В Blazor в этом нет необходимости, поскольку вместо этого вы можете использовать код с циклом, чтобы иметь такое окружающее поведение. Затем я понял, что мог бы сделать то же самое здесь. Вместо этого я мог бы получить модель из метаданных в коде и передать ее в обычный компонент. @{ var inputModel = InputModelHelper.GetFor(ViewModel, x => x.NewTodo); } <InputBasic DataItem="@inputModel"/>

Может быть, для некоторых это очевидно ... поэтому шаблоны на самом деле не являются абсолютно необходимыми для моего варианта использования. Но у меня возник вопрос, чего я действительно хочу. Может быть, это больше похоже на добавление атрибутов и содержимого в существующие элементы? И, возможно, сделав это, имея возможность определить настраиваемый атрибут, например:

  <label For=”@DataSource(Model, x => x.NewTodo)”></label>
  <input For=”@DataSource(Model, x => x.NewTodo)” />

Чтобы проверить, возможно ли даже добавить узлы атрибутов в существующий элемент, я создал функцию (возвращает пустую строку для этого узла содержимого) после открытого тега <label>@For(x => x.NewTodo)</label> который расширяет процесс визуализации, добавляя узлы атрибутов (и содержимое node в случае элемента label) строителю. (Конструктор был захвачен в методе BuildRenderTree.) Построитель имеет позицию узла метки, поэтому в его буфере можно легко увидеть, установлен ли атрибут, и поэтому не должен быть отменен этим механизмом. Это действительно работает. Это немного сложнее для входного элемента, поскольку это пустой элемент (его нужно снова открыть, а функцию нельзя поместить среди атрибутов), но в качестве доказательства концепции он, похоже, работает. Как можно сделать так, чтобы это поддерживалось, и имеет ли это смысл?

Я предполагаю, что эту функцию продолжения / расширения рендеринга можно подключить в нескольких местах. Чтобы заставить вас задуматься ... возможно, у вас есть возможность внедрить «RenderElementExtensionHandler», и если компонент имеет такое свойство, тогда его вызов компилируется в позицию, когда все атрибуты были добавлены, и после закрытия в сгенерированном коде тех элементов, которые имеют настраиваемые атрибуты, например For в данном случае.
Это то, что можно использовать для других вещей, или это крайний случай? Есть ли другие идеи расширения поведения на существующие элементы?

Извините, если это получилось слишком долго ...

Почему бы не рендерить реквизит?

<List RenderHeader={(text) => (<h4>{text}</h4>)}>
    {({ item, index }) => (
        <li value={index} class={item.Type}>
            {item.Content}
        </li>)}
</List>

Выполнено:
b2c8b1aec98e65054c590d356fdf15e6a5e6a822
409026c2f41926d85d18ea2abf345453fa083e34
ce11f517d370b501433a62a56fbd4be1704000ad

Была ли эта страница полезной?
0 / 5 - 0 рейтинги