注意,从这里开始,往后的文章中,Blazor项目指的都是Blazor Web App下的Server模式。
组件的基类
组件是使用 C# 和 HTML 标签的组合在 Razor 组件文件(文件扩展名为 .razor
)中实现的。
默认情况下,ComponentBase
是Razor组件的基类,该基类实现了组件的最低抽象接口,即IComponent
接口,定义了组件最基本的属性和方法,例如,处理一组内置组件生命周期事件。
- 可以看出,实际上组件就是一个C#类。
开发人员可以通过 Razor 组件文件 (.razor
) 来构建 Razor 组件或者通过创建C#类型并继承ComponentBase
来构建组件。此外,也可以通过实现IComponent
来生成组件,从而对组件更加高度自定义的控制,但是实现IComponent
就需要开发人员使用事件和生命周期方法手动触发渲染,且这些事件和方法必须由开发人员创建和维护。
Razor 语法
组件中可以使用 Razor 语法,其中会较为频繁使用了两个 Razor 功能,即指令和属性指令。
指令
Razoe指定用于更改组件标记的分析或运行方式。 例如,[@page](https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/razor?view=aspnetcore-8.0#page)
指令使用路由模板指定可路由组件,可以由用户请求在浏览器中按特定 URL 直接访问。
官方给了在组件中使用各种指令时的排放顺序规范,但并不是必须这么去排序的,具体如下:
@page
@rendermode
(.NET 8 或更高版本)@using
System
命名空间(字母顺序)Microsoft
命名空间(字母顺序)- 第三方 API 命名空间(字母顺序)
- 应用命名空间(字母顺序)
- 其他指令(字母顺序)
然后,指令和Razor标记的第一行之间,留一个空白行。
-
示例
@page "/doctor-who-episodes/{season:int}" @rendermode InteractiveWebAssembly @using System.Globalization @using System.Text.Json @using Microsoft.AspNetCore.Localization @using Mandrill @using BlazorSample.Components.Layout @attribute [Authorize] @implements IAsyncDisposable @inject IJSRuntime JS @inject ILogger<DoctorWhoEpisodes> Logger<PageTitle>Doctor Who Episode List</PageTitle>...
属性指令
属性指令一般也是用于更改嘴贱元素的分析方式或运行方式的。
-
示例
<input @bind="episodeId" />
组件的名称、类名和命名空间
命名规则
- 组件的名称必须以大写字符开头。
- 文件路径和文件名建议使用Pascal大小写,例如
Components/Pages/ProductDetail.razor
- 可路由组件的URL路径,建议使用kebab大小写,例如
"/product-detail"
命名空间规则
- 可路由的组件通常位于
Components/Pages
文件夹中。 - 非可路由组件(也就是没有用
@page
指令的组件)通常放置在Components
文件夹或添加到项目的自定义文件夹中。 - 组件的命名空间是从应用的根命名空间和该组件在项目内的位置(文件夹)派生而来的。 例如应用的根命名空间是
BlazorSample
,并且Counter
组件位于Components/Pages
文件夹中:Counter
组件的命名空间为BlazorSample.Components.Pages
。组件的完全限定类型名称为BlazorSample.Components.Pages.Counter
- 可以使用完全限定名来引用组件,这时不需要
@using
命令导入命名空间,例如:<BlazorSample.Components.Pages.Counter/>
。 - 对于保存组件的自定义文件夹,最好使用
@using
指令添加到父组件或项目的_Imports.razor
文件全局引用。需要注意的是,_Imports.razor
文件中的@using
指令仅适用于 Razor 文件 (.razor
),而不适用于 C# 文件 (.cs
)。 - 不支持
global::
。 - 不支持使用
@using
只引入部分命名空间,然后补全引用组件。例如,组件Components/Layout/NavMenu.razor
,不能在要使用NavMenu组件的组件上,通过@using BlazorSample.Components
引入部分命名空间,然后通过<Layout.NavMenu></Layout.NavMenu>
来引用组件。 - 不支持
@using
的别名指定,例如不支持@using AAA=BlazorApp7.Components.Layout
支持分部类
Blazor项目中的组件,本质就是继承了ComponentBase
的C#类,既然是C#类,那么就支持分部类。
一般情况下,为了方便,Razor标签、HTML和@code
块都放在同一个.razor
文件中,例如Blazor项目模板中生成的Counter.razor组件。但当我们组件中相关业务逻辑较多较复杂时,全部都写在一个.razor文件中不免显得有些臃肿,这个时候就可以通过分部类,将C#相关的代码放置到部分类中。
组件的分部类的文件名建议名为xxxx.razor.cs
,类似css隔离的用法。以项目模板中的Counter.razor组件为例子,将其中的计数方法和属性剥离到分部类中:
-
Counter.razor
@page "/counter" @rendermode InteractiveServer<PageTitle>Counter</PageTitle><h1>Counter</h1><p role="status">Current count: @currentCount</p><button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
-
Counter.razor.cs
using Microsoft.AspNetCore.Components;namespace BlazorServer.Components.Pages {public partial class Counter{private int currentCount = 0;private void IncrementCount(){currentCount++;}} }
指定基类
@inherits指令用于指定组件的基类。 与使用分部类不同,分部类仅在 C# 逻辑上拆分组件,而使用基类可以继承 C# 代码,以便在具有共同特征的组件之间共用基类的属性和方法。
-
BlazorRocks.razor
@page "/blazor-rocks-" @inherits BlazorRocksBase<PageTitle>Blazor Rocks!</PageTitle><h1>Blazor Rocks! Example 1</h1><p>@BlazorRocksText </p>
-
BlazorRocksBase.cs
using Microsoft.AspNetCore.Components;namespace BlazorSample;public class BlazorRocksBase : ComponentBase {public string BlazorRocksText { get; set; } ="Blazor rocks the browser!"; }
async方法不支持返回void
Blazor框架不跟踪返回void
的异步方法 (async
),如果返回void,则不会捕获异常。 因此,建议始终从异步方法返回Task
。
组件实例的引用
Blazor提供了一种引用组件实例的方法,为的是方便在使用组件时,父组件可以通过组件实例调用对应的方法,从而更加灵活的使用组件。
使用方式
注意
组件引用仅在渲染组件后才开始赋值。在组件渲染完成之前,没有任何可以引用的内容,因此如果要调用组件引用中的方法时,要考虑正确的调用时机。
- 不要直接在组件的事件中调用组件引用的方法,因为在分配单击事件时可能不会分配引用变量,例如
<MyComponent @onclick="childComponent!.ChildMethod(5)"/>
。 - 若要在组件完成渲染后操作组件引用,可以使用
OnAfterRender
或OnAfterRenderAsync
方法。
示例
-
ReferenceChild.razor
@inject ILogger<ReferenceChild> Logger@if (value > 0) {<p><code>value</code>: @value</p> }@code {private int value;public void ChildMethod(int value){Logger.LogInformation("Received {Value} in ChildMethod", value);this.value = value;StateHasChanged();} }
-
ReferenceParent.razor
@page "/reference-parent"<div><button @onclick="@(() => childComponent1!.ChildMethod(5))">Call <code>ReferenceChild.ChildMethod</code> (first instance) with an argument of 5</button><ReferenceChild @ref="childComponent1"/> </div><div><button @onclick="CallChildMethod">Call <code>ReferenceChild.ChildMethod</code> (second instance) with an argument of 5</button><ReferenceChild @ref="childComponent2"/> </div>@code {private ReferenceChild? childComponent1;private ReferenceChild? childComponent2;private void CallChildMethod(){childComponent2!.ChildMethod(5);} }