Templating components with RenderFragments

使用RenderFragment传HTML片段。

直接如下使用component会出错:

<Collapsible>Hello world!</Collapsible>

抛出的异常可能是:WASM: System.InvalidOperationException: Object of type ‘TemplatedComponents.Components.Collapsible’ does not have a property matching the name ‘ChildContent’.

需要在Collapsible组件中指定一个额外的参数ChildContent,用于接收片段:

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

此处的ChildContent用于接收传入的渲染片段。可以是纯文本,可以是HTML片段,可以是razor片段。

可以传多个RenderFragment:

<MyComponent>
  <Header>
    <h1>The header</h1>
  </Header>
  <Footer>
    This is the footer
  </Footer>
  <ChildContent>
    The ChildContent render fragment must now be explicitly named because we have
    more than one render fragment parameter in MyComponent.

    It doesn't have to be named ChildContent.
  </ChildContent>
</MyComponent>

Creating a TabControl

  • 通过RenderFragment传数据
  • 通过CacadingParameter来将上级TabControl传入到下级Tabpage组件中

顶层代码是:

<TabControl>
  <TabPage Text="Tab 1">
    <h1>The first tab</h1>
  </TabPage>
  <TabPage Text="Tab 2">
    <h1>The second tab</h1>
  </TabPage>
  <TabPage Text="Tab 3">
    <h1>The third tab</h1>
  </TabPage>
</TabControl>

内容是这么传递的,首先TabControl标签中的所有内容作为RenderFragment都传给TabControl组件的ChildContent辖属。TabControl只需在页面上输入ChildContent,然后Blazor会自动初始化三个tabPage,每个TabPage又被传入相应的RenderFragment(例如<h1>The first tab</ha>)。当然,每个TabPage也要有ChildContent辖属来接收其RenderFragment。

Tab标签栏由上级的TabControl控制,所以TabControl需要知道有几个下级的TabPage,以及每个TabPage的标签名。前者是让TabPage初始化时告诉TabControl的,后者是使用TabPage上设置的Text属性。也就是说,每个TabPage需要知道上级是谁,这是TabControl使用CacadingParameter,把自己(this)传递给下级TabPage,然后每个下级TabPage又将自己注册给TabControl。

TabControl记录当前那个标签页是活跃的,并将其保存在一个辖属中。下级TabPage只需要判断自己是否是上级的活跃标签页那个辖属,就知道自己的内容该不该显示了。

Passing data to a RenderFragment

对上面做一些修改:

<TabControl>

    <TabTextTemplate>
    @context.Text
    </TabTextTemplate>

  <ChildContent>
    <TabPage Text="Tab 1">
      <h1>The first tab</h1>
    </TabPage>
    <TabPage Text="Tab 2">
      <h1>The second tab</h1>
    </TabPage>
    <TabPage Text="Tab 3">
      <h1>The third tab</h1>
    </TabPage>
  </ChildContent>
</TabControl>

传入两个RenderFragment

  • 一个是类型为RenderFragment的childContent
  • 另一个是RenderFragment的TabTextTemplate
    • TabTextTemplate 对应上述的<TabTextTemplate>标签
    • 此处的TabPage既是@context.Text中的context。如果不想用context,也可以改成其他名字:<TabTextTemplate Context="TheTab">
    • 无他,维函数传递而已

Using @typeparam to create generic components

将组件定义成泛型的需要特定的语法。

假设有一个DataList.razor,需要定义成泛型的,那么需要使用@typeparam指示,以及定义一个IEnumerable类型的辖属:

@typeparam TItem

@code
{
  [Parameter]
  public IEnumerable<TItem> Data { get; set; }
}

下面定义一个Person类,作为其TItem的类型:

public class Person
{
  public string Salutation { get; set; }
  public string GivenName { get; set; }
  public string FamilyName { get; set; }
}

DataList的组件是这样使用的:

@page "/"

<h1>A generic list of Person</h1>
<DataList Data=@People>
  @context.Salutation @context.FamilyName, @context.GivenName
</DataList>

@code
{
  private IEnumerable<Person> People;
  protected override void OnInitialized()
  {
    base.OnInitialized();
    People = new Person[]
    {
      new Person { Salutation = "Mr", GivenName = "Bob", FamilyName = "Geldof" },
      new Person { Salutation = "Mrs", GivenName = "Angela", FamilyName = "Rippon" },
      new Person { Salutation = "Mr", GivenName = "Freddie", FamilyName = "Mercury" }
    };
  }
}

上述代码片段中,<DataList Data=@People>中的People符合IEnumerable<TItem>类型。然后Data是组件中用于接收People的辖属。此处TItem是从People的类型推断出来的,也可以显示指定:<SomeGenericComponent TParam1=Person TParam2=Supplier TItem=etc/>

DataList对应的ChildContent是@context.Salutation @context.FamilyName, @context.GivenName,可以把这个看成一个函数,输入时IEnumerable<TItem>中的一项,然后对其处理后。

可以在DataList.razor显示调用ChildContent:

@typeparam TItem
<ul>
  @foreach(TItem item in Data ?? Array.Empty<TItem>())
  {
    <li>@ChildContent(item)</li>
  }
</ul>
@code
{
  [Parameter]
  public IEnumerable<TItem> Data { get; set; }

  [Parameter]
  public RenderFragment<TItem> ChildContent { get; set; }
}

Passing placeholders to RenderFragments

假设有这样一个参数,其类型为RenderFragment<RenderFragment>

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

RenderFragment<T>中的<T>是按用户自定义的标签传入@context变量的。但是在layout中,其对应的是@Body,如果将@Body.GetType().Name打印出来,那么可以看到输出的是RenderFragment

@:@{在C#代码中插入Razor代码。

假设将RenderFragment看出一个可执行函数,如果RenderFragment<T>中的<T>部分缺失则代表此RenderFragment执行的时候不需要额外的参数;如果指定了<T>,那么必须传入类型为T的参数。

这些参数应该不是在构造组件的时候传入的,而是在组件构造完毕时传递给组件的指定辖属。

(本篇完)