Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

19 Aug 2021

Blazor文档阅读【九】

Blazor 》 Component。

ASP.NET Core Razor components

一个组件是自包含的逻辑合集用于UI渲染和交互。组件可以嵌套,复用,共享,或在MVC以及Razor Page应用中使用。

Component classes

组件以Razor编写,也就是在HTML中糅杂C#。Razor中大量地使用了指示,以及指示属性:

  • 指示,改变组件中语标的解析方式或者工作方式。
  • 指示属性,改变组件中元素的解析方式或者工作方式

Razor语句相关的引用文档,参考https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-6.0

组件名必须以大写字母开头,也就是Pascal形式(首字母大写的驼峰形式)。但是URL却经常小写,然后单词以横线分隔。

路由使用@page指示:@page "/hello-world"

插入大片代码可以使用@code

C#默认类成员是private的

组件会Blazor转化为render tree , 是DOM和CSSOM的组合。

代表页面的组件通常放置在Pages目录,其他组件通常放置在Shared或者自定义目录。

组件的命名空间通常从App的根命名空间以及组件的目录得出。若项目BlazorSample中有一个组件Counter位于Pages目录,其命名空间是:BlazorSample.Pages。可以修改_Imports.razor来添加并打开命名空间:@using BlazorSample.Components

@using只适用于.razor而不适用于.cs。

也可以使用组件的完全修饰名:<BlazorSample.Components.ProductDetail />

组件的命名空间的决定顺序:

  • .razor中的@namespace指示
  • 工程文件中指定根命名空间:<RootNamespace>BlazorSample</RootNamespace>
  • 工程的名字(比如.csproj)的名字作为根命名空间
  • C#的名字绑定规则适用于组件,对于组件{PROJECT ROOT}/Pages/Index.razor
    • 可访问Pages目录下的其他组件
    • 可访问根目录下的没有指定其他命名空间的组件

下列不受支持:

  • global::
  • @using Foo = Bar
  • @using BlazorSample,但是想使用<Shared.NavMenu>

组件生成的是C# partial classes ,可以支持:

  • 所有标辞和代码均在一个.razor文件
  • 除了.razor文件,可以把代码放置在.cs文件

组件可以有自己的.css文件

组件可以从另一个组件中派生:@inherits BlazorRocksBase

Component parameters

组件的参数通过标为[Parameter]的辖属接收。

可以给入参指定默认值,但是不要在组件渲染之后自己修改参数

与Razor pages(.cshtml)不同,Blazor无法在渲染组件的时候在Razor表达式中执行异步操作,所以下面的Razor语句不受支持,并且会产生错误:

<ParameterChild Title="@await ..." />

异步操作可以在异步的生命周期事件中执行,参考https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-6.0

下面的用法在.cshtml中可用,但在.razor中不受支持,并会产生错误:

<ParameterChild Title="Set by @(panelData.Title)" />

解放是造一个C#函数来处理上述的字符串拼接,更多参考https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-6.0

组件参数必须是自动辖属,它们的get和set访问器不能自带逻辑。若要对辖属进行变换,可以在组件参数辖属上套一个非参数辖属,或者在OnParametersSetAsync中变换。

对于组件参数,可以额外指定[EditorRequired]来说明其必要性。此属性只在设计和编译时有效,在运行时无效。所以无法保证参数不是null的。如下所示:

[Parameter, EditorRequired]
public string Title { get; set; }

Route parameters

路由中也可以指定参数,如@page "/route-parameter/{text?}",需要指定一个对应的承接辖属:

    [Parameter]
    public string Text { get; set; }

Overwritten parameters

Blazor保证上级到下级的参数传递是可靠的:

  • 参数不会无故被覆写
  • 副作用最小化,比如避免可能导致无线循环的额外的渲染

意外的覆写可能发生:

  • 下级组件承接了若干个上级组件传下的参数
  • 下级组件直接将值写入这些个参数
    • 写值有可能是辖属的set方法
  • 上级组件渲染时又覆写了这些个参数

组件最好不要直接写值给承接参数

Child content

标记内容传递给一个特定的组件辖属:

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

在for循环中,如果循环变量用于组件内容,需要使用其他变量中转以下:

@for (int c = 0; c < 3; c++)
{
    var current = c;

    <RenderFragmentChild>
        Count: @current
    </RenderFragmentChild>
}

使用foreach则没有这个问题:

@foreach (var c in Enumerable.Range(0,3))
{
    <RenderFragmentChild>
        Count: @c
    </RenderFragmentChild>
}

WebAssembluy参考https://docs.microsoft.com/en-us/aspnet/core/blazor/webassembly-performance-best-practices?view=aspnetcore-6.0#define-reusable-renderfragments-in-code

Attribute splatting and arbitrary parameters

Attribute splatting是指将额外的属性以字典的方式传入组件,需要用到@attributes指示:

<input id="useAttributesDict"
       @attributes="InputAttributes" />

承接辖属如下:

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; }
}

一个组件只能指定一个CaptureUnmatchedValues的辖属,而且此辖属的类型必须是兼容Dictionary<string, object>的,比如IEnumerable<KeyValuePair<string, object>>或者IReadOnlyDictionary<string, object>

@attributes的摆放位置是有讲究的。

Capture references to components

通过@ref属性可以捕捉子组件的引用,并且将其赋给一个辖属。引用只可在OnAfterRender or OnAfterRenderAsync后使用。也就是说,不能调用所引用的组件的方法。可以使用lambda延缓调用时机,或者在OnAfterRender or OnAfterRenderAsync中处理。

例如:

@page "/reference-parent-1"

<button @onclick="@(() => childComponent.ChildMethod(5))">
    Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
    private ReferenceChild childComponent;
}

如果组件时循环创建的,那么承接的辖属必须是:

private List<ReferenceChild> components = new();

不要使用组件引用去修改下级组件的状态

Synchronization context

Blazor使用SynchronizationContext来确保单线程运行。组件的生命周期方法以及事件回调均在SynchronizationContext中处理。

即便是Server应用,也是模仿单线程,为了根WebAssembly的模型匹配。所以下列阻塞线程的调用要避免使用:

  • Result
  • Wait
  • WaitAny
  • WaitAll
  • Sleep
  • GetResult

对于外部事件,比如定时器或者其他通知,使用InvokeAsync方法来将执行代码分派到SynchronizationContext执行。文中带有一个示例。

Use @key to control the preservation of elements and components

可以使用@key来增加渲染的HTML元素到组件之间的对应关系。

用作@key的标的要稳定,一旦其发生变化,组件要重新渲染。

使用@key元素只在同一元素下才能互相比对

如何挑选@key的值:

  • 模型标的现例
  • 唯一的标识符

若key之间存在冲突,Blazor会抛出异常

Apply an attribute

可以在组件上通过@attribute添加属性,以[Authorize]为例:

@page "/"
@attribute [Authorize]

Conditional HTML element attributes

HTML element attribute properties are conditionally set based on the .NET value. If the value is false or null, the property isn’t set. If the value is true, the property is set

更多参考https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-6.0

Raw HTML

MarkupString类型的值会被当作HTML或者SVG片段解析后插入DOM,示例:

@page "/markup-string-example"

@((MarkupString)myMarkup)

@code {
    private string myMarkup =
        "<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}

Razor templates

Razor模板形如:

@<{HTML tag}>...</{HTML tag}>

使用示例:

@page "/razor-template"

@timeTemplate

@petTemplate(new Pet { Name = "Nutty Rex" })

@code {
    private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}

Static assets

Blazor的物料存放在wwwroot目录下。可以使用/引用。对于{project root}/wwwroot/images下的logo.png,引用路径如下:

<img alt="Company logo" src="/images/logo.png" />

组件不支持tilde-slash(即~/

Tag Helpers aren’t supported in components

Blazor不支持MVC中的Tag Helper。

Scalable Vector Graphics (SVG) images

略。

Whitespace rendering behavior

若非@preservewhitespace的值为真,额外的空白会被削除:

  • 元素前后的空白
  • childcontent的前后空白
  • C#代码块,比如@if或者@foreach前后的空白

若要取消对空白的削除,可以在组件或者_Imports.razor中设置@preservewhitespace true

Generic type parameter support

使用@typeparam为组件设置一个泛型参数,支持C#的where类型约束:

@typeparam TEntity where TEntity : IEntity

(本篇完)

Categories