四、了解基本 Blazor 组件
在本章中,我们将了解 Blazor 模板附带的组件,以及开始构建我们自己的组件。了解用于构建 Blazor 网站的不同技术将有助于我们开始构建组件。
Blazor 将组件用于大多数事情,因此我们将在整本书中使用从本章中获得的知识。
我们将从理论开始这一章,并以创建一个组件来显示一些博客文章结束,该组件使用我们在上一章中创建的应用编程接口, 第 3 章介绍实体框架核心。
在本章中,我们将涵盖以下主题:
- 探索组件
- 学习 Razor 语法
- 因素
- 编写我们的第一个组件
技术要求
在本章中,我们将开始构建我们的组件,为此,您将需要我们在上一章中创建的代码, 第 3 章引入实体框架核心。如果你已经按照前几章的说明去做了,那么你就可以走了;如果没有,那么一定要克隆/下载 repo。本章的起点可以在ch3
文件夹中找到,完成的代码可以在ch4
中找到。
你可以在https://github . com/PacktPublishing/Web-Development-wit-Blazor/tree/master/chapter 04找到本章最终结果的源代码。
在本章中,我们将使用 Blazor 服务器项目,因此请确保右键单击 MyBlogServerSide 项目,并选择设置为启动项目。
探索组件
在 Blazor 中,组件是一个.razor
文件,它可以包含一个小的、独立的功能(代码和标记)或者可以用作页面本身。一个组件也可以承载其他组件。本章将向我们展示组件如何工作以及如何使用它们。
有三种不同的方法我们可以创建一个组件:
- 使用 Razor 语法,代码和 HTML 共享同一个文件
- 将代码隐藏文件与
.razor
文件一起使用 - 仅使用代码隐藏文件
我们将考虑不同的选择。我们接下来要浏览的模板都使用第一个选项,.razor
文件,其中代码和 HTML 混合在同一个文件中。
模板中的组件如下:
counter
FetchData
计数器
counter
页面显示一个按钮和一个计数器;如果我们按下按钮,计数器就会增加。我们现在将这一页拆开,以便更容易理解。
页面顶部是@page
指令,它使得直接路由到组件成为可能,正如我们在这段代码中看到的:
@page "/counter"
如果我们启动MyBlogServerSide
项目并将/counter
添加到 URL 的末尾,我们会看到我们可以通过使用组件的路由来直接访问它。我们也可以让路线采用参数,但我们过一会儿会回到这个问题。
接下来,让我们探索一下代码。要向页面添加代码,我们使用@code
语句,在该语句中,我们可以添加普通的 C#代码,如图所示:
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
在前面的代码块中,我们有一个私有的currentCount
变量,它被设置为0
。然后我们有一个叫做IncrementCount()
的方法,它将currentCount
变量增加1
。
我们使用@
符号显示当前值。在 Razor 中,@
符号表示是时候进行一些编码了:
<p>Current count: @currentCount</p>
正如我们所看到的,Razor 非常聪明,因为它理解代码何时停止,标记何时继续,所以不需要添加额外的东西来从代码过渡到标记(下一节将详细介绍)。
正如我们在前面的例子中所看到的,我们将 HTML 标签与@currentCount
混合在一起,Razor 理解了其中的区别。接下来,我们有一个按钮,它是更改值的触发器:
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
这是一个带有引导类的 HTML 按钮(让它看起来更好一点)。@onclick
正在将按钮的 onclick 事件绑定到IncrementCount()
方法。如果我们使用onclick
,没有@
,它将引用 JavaScript 事件而不起作用。所以,当我们按下按钮时,它会调用IncrementCount()
方法(由图 4.1 中的 1 描绘),该方法递增变量(由 2 描绘),通过改变变量,UI 会自动更新(由 3 描绘),如图图 4.1 :
图 4.1–计数器组件的流量
对于 Blazor 网络组件和 Blazor 服务器来说,counter
组件的实现方式是相同的。FetchData
组件有两种不同的实现方式,原因很简单,Blazor Server 项目可以直接访问服务器数据,Blazor WebAssembly 需要通过 web API 访问。
我们对我们的应用编程接口使用相同的方法,这样我们就能感受到如何利用依赖注入 ( DI )以及当我们使用 Blazor 服务器时如何直接连接到数据库。
获取数据
下一个组件我们来看看是FetchData
组件。它位于Pages/FetchData.razor
文件夹中。
对于 Blazor WebAssembly 和 Blazor Server 来说,FetchData
组件的主要实现看起来是一样的。这两个版本中,文件的顶部行以及获取数据的方式都有所不同。对于 Blazor 服务器,它看起来像这样:
@page "/fetchdata"
@using MyBlogServerSide.Data
@inject WeatherForecastService ForecastService
它定义了一个路由,添加了一个名称空间,并注入了一个服务。我们可以在MyBlogServerSide
项目的Data
文件夹中找到服务。
该服务是一个创建一些随机预测数据的类;代码如下所示:
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync (DateTime startDate)
{
var rng = new Random();
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray());
}
}
正如我们所看到的,它会生成摘要并对温度进行随机化。
在FetchData
组件的code
部分,我们将找到调用服务的代码:
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
代码将从服务中获取数据,并填充一个名为forecasts
的WeatherForecast
数组。
在MyBlogWebAssembly.Client
项目中,情况看起来有些不同。首先,文件的前几行如下所示:
@page "/fetchdata"
@using MyBlogWebAssembly.Shared
@inject HttpClient Http
该代码使用page
指令定义了一个路由,向我们的共享库中添加了一个名称空间,并注入了HttpClient
而不是服务。HttpClient
用来从服务器获取数据,这是一个比较现实的现实场景。
HttpClient
在Program.cs
文件中定义,并且具有与MyBlogWebAssembly.Server
项目相同的基址,因为服务器项目托管客户端项目。
获取数据如下所示:
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync <WeatherForecast[]>("WeatherForecast");
}
代码将获取数据并填充一个名为forecasts
的WeatherForecast
数组。但是我们没有从服务中获取数据,而是拨打了"WeatherForecast"
的网址。我们可以在MyBlogWebAddembly.Server
项目中找到 web API。
控制器(Controllers/WeatherForcastController.cs
)看起来像这样(与服务有很多相似之处):
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> logger;
public WeatherForecastController (ILogger<WeatherForecastController> logger)
{
this.logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
看起来和服务差不多,但是是作为网络应用编程接口实现的。由于两个版本中的数据看起来相同,因此获取数据(在两种情况下)将使用天气预报数据填充数组。
在Pages/FetchData.razor
中,显示天气数据的代码在 Blazor WebAssembly 和 Blazor Server 中都是这样的:
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()
</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
正如我们所看到的,通过使用 Razor 语法,我们可以无缝地将代码与 HTML 混合在一起。代码检查是否有任何数据——如果有,那么它将呈现该表;如果没有,将显示加载信息。我们完全控制 HTML,Blazor 不会在生成的 HTML 中添加任何内容。
有一些组件库可以让这个过程变得简单一点,我们将在下一章 第 5 章创建高级 Blazor 组件中了解一下。
现在我们知道了示例模板是如何实现的,是时候深入研究 Razor 语法本身了。
学习 Razor 语法
我喜欢 Razor 语法的一点是它很容易混合代码和 HTML 标签。这一部分将有很多理论来帮助我们了解 Razor 语法。
要从 HTML 过渡到代码(C#),我们使用@
符号。有几种方法可以将代码添加到文件中:
- Razor 代码块
- 隐式 Razor 表达式
- 显式剃刀表达式
- 表达式编码
- 指令
Razor 代码块
我们已经看到了一些代码块。一个code
块看起来是这样的:
@code {
//your code here
}
如果我们愿意,我们可以跳过code
关键字,就像这样:
@{
//your code here
}
在这些大括号中,我们可以像这样混合 HTML 和代码:
@{
void RenderName(string name)
{
<p>Name: <strong>@name</strong></p>
}
RenderName("Steve Sandersson");
RenderName("Daniel Roth");
}
注意RenderName()
方法是如何从代码转换到段落标记并返回代码的;这是一个隐含的转变。
如果我们想要输出没有 HTML 标签的文本,我们可以使用text
标签来代替段落标签,如下例所示:
<text>Name: <strong>@name</strong></text>
这将呈现与前面代码所示相同的结果,但没有段落标记。不会渲染text
标签。
隐式剃刀表达式
当我们在 HTML 标签中添加代码时,隐式 Razor 表达式就是。
我们已经在FetchData
例子中看到了这一点:
<td>@forecast.Summary</td>
我们从一个<td>
标签开始,然后使用@
符号切换到 C#,然后回到 HTML 作为结束标签。我们可以在方法调用中使用await
关键字,但是除此之外,隐式 Razor 表达式不能包含任何空格。
我们不能使用隐式表达式来调用通用的方法,因为<>
会将解释为 HTML。因此,为了解决这个问题,我们可以使用显式表达式。
明确的剃刀表达式
如果我们想在代码中使用空格,我们可以使用显式 Razor 表达式。只需用@
符号后跟括号( )
来编写代码。所以,它看起来像这样:@()
。
在这个例子中,我们从当前日期减去7
天:
<td>@(DateTime.Now - TimeSpan.FromDays(7))</td>
我们还可以使用显式 Razor 表达式来连接文本;例如,我们可以这样连接文本和代码:
<td>Temp@(forecast.TemperatureC)</td>
输出将是<td>Temp42</td>
。
使用显式表达式,我们可以使用以下语法轻松调用泛型方法:
<td>@(MyGenericMethod<string>())</td>
Razor 引擎知道我们是否在使用代码。它还确保在输出到浏览器时将字符串编码为 HTML,称为表达式编码。
表达编码
如果我们把 HTML 作为一个字符串,默认会被转义。以这个代码为例:
@("<span>Hello World</span>")
呈现的 HTML 如下所示:
<span>Hello World</span>
要从字符串中输出实际的 HTML(这是我们稍后要做的事情),您可以使用以下语法:
@((MarkupString)"<span>Hello World</span>")
通过使用MarkupString
,输出将是 HTML,它将显示 HTML 标记跨度。在某些情况下,一行代码是不够的;然后我们可以使用代码块。
指令
有一堆指令改变了组件被解析的方式或者可以启用功能。这些是跟随@
符号的保留关键字。我们将讨论最常见和最有用的。
我们可以使用代码隐藏来编写代码,以便在代码和布局之间获得更多的分离。我发现布局和代码在同一个.razor
文件中是非常好的。在本章的后面,我们将看看如何使用代码隐藏,而不是使用 Razor 语法。
在下面的例子中,我们将看看如何使用代码隐藏来做同样的事情。
添加属性
要给我们的页面添加一个属性,我们可以使用attribute
指令:
@attribute [Authorize]
如果我们使用代码隐藏文件,我们将使用以下语法:
[Authorize]
添加接口
为了实现一个接口(在本例中为IDisposable
,我们将使用以下代码:
@implements IDisposable
然后我们将在@code{}
部分实现接口需要的方法。
为了在代码隐藏场景中做同样的事情,我们将在类名之后添加接口,如下例所示:
public class SomeClass : IDisposable {}
继承
要继承另一个类,我们应该使用以下代码:
@inherits TypeNameOfClassToInheritFrom
为了在代码隐藏场景中做到这一点,我们将在类名之后添加我们想要继承的类:
public class SomeClass : TypeNameOfClassToInheritFrom {}
无商标消费品
我们可以将我们的组件定义为通用组件。
泛型让我们可以制作让我们定义数据类型的组件,所以组件可以和任何数据类型一起工作。
为了将组件定义为通用组件,我们添加了@typeparam
指令;然后我们可以像这样在组件的代码中使用类型:
@typeparam TItem
@code
{
[Parameter]
public List<TItem> Data { get; set; }
}
在创建可重用组件时,泛型超级强大,我们将在 第 6 章中回到泛型,通过验证构建表单。
更改布局
如果我们想要一个页面的特定布局(不是app.razor
文件中指定的默认布局),我们可以使用@layout
指令:
@layout AnotherLayoutFile
这样,我们的组件将使用指定的布局文件(这仅适用于具有@page
指令的组件)。
设置命名空间
默认情况下,组件的命名空间将是我们项目的默认命名空间的名称,加上文件夹结构。如果我们希望我们的组件在一个特定的名称空间中,我们可以使用以下内容:
@namespace Another.NameSpace
设置路线
我们已经触及了@page
指令。如果我们希望使用网址直接访问我们的组件,我们可以使用@page
指令:
@page "/theurl"
该网址可以包含参数、子文件夹等,我们将在本章稍后部分讨论这些内容。
添加 using 语句
为了给我们的组件添加一个名称空间,我们可以使用@using
指令:
@using System.IO
如果我们在许多组件中使用了名称空间,那么我们可以将它们添加到_Imports.razor
文件中。这样,它将在我们创建的所有组件中可用。
现在我们更了解 Razor 语法是如何工作的。别担心,我们会有足够的时间练习的。还有一个指令我在这一部分没有涉及到,那就是inject
。我们已经看过几次了,但是为了涵盖所有的基础,我们首先需要了解什么是 DI 以及它是如何工作的,我们将在下一节中看到。
理解依赖注入
DI 是一种软件模式,是实现控制反转 ( IoC )的技术。
IoC 是一个通用术语,意思是我们可以指示类需要一个类实例,而不是让我们的类实例化一个对象。我们可以说我们的类要么想要一个特定的类,要么想要一个特定的接口。类的创建在别处,它将创建什么类取决于 IoC。
说到 DI,它是 IoC 的一种形式,对象(类实例)通过构造函数、参数或服务查找来传递。
在 Blazor 中,我们可以通过提供实例化对象的方法来配置 DI。在 Blazor 中,这是我们应该使用的关键架构模式。我们已经看到了一些对它的引用,例如在Startup.cs
中:
services.AddSingleton<WeatherForecastService>();
这里,我们说如果任何类想要WeatherForecastService
,应用应该实例化一个WeatherForecastService
类型的对象。在这种情况下,我们不使用接口;相反,我们可以创建一个接口,并这样配置它:
services.AddSingleton<IWeatherForecastService ,WeatherForecastService>();
在这种情况下,如果一个类请求IWeatherForecastService
的实例,应用将实例化一个WeatherForecastService
对象并返回它。我们在上一章 第三章介绍实体框架核心做了这个。我们创建了一个返回MyBlogApiServerSide
实例的IMyBlogApi
界面;当我们实现 WebAssembly 版本时,DI 将返回另一个类。
使用 DI 有很多好处。我们的依赖关系是松散耦合的,这意味着我们不会实例化类中的另一个类。相反,我们要求一个实例,这使得编写测试以及根据平台改变实现变得更加容易。
因为我们需要将它们传递到类中,所以任何外部依赖都会更加清晰。我们还可以设置在中心位置实例化对象的方式。我们在Startup.cs
(针对 Blazor 服务器)和Program.cs
(针对 WebAssembly)中配置 DI。
我们可以用不同的方式配置对象的创建,例如:
- 一个
- 审视
- 短暂的
一个
当我们使用 singleton 时,我们网站的所有用户的对象都是一样的。该对象将只创建一次。
要配置单例服务,请使用以下命令:
services.AddSingleton<IWeatherForecastService ,WeatherForecastService>();
当我们想要与我们站点的所有用户共享我们的对象时,我们应该使用 singleton,但是要注意状态是共享的,所以不要存储任何与特定用户或用户偏好相关的数据,因为这会影响所有用户。
审视
当我们使用 scoped 时,将为每个连接创建一个新的对象,因为 Blazor Server 需要一个连接才能工作,所以只要用户有连接,它就是同一个对象。WebAssembly 没有作用域的概念,因为没有建立连接,所以所有代码都在用户的 web 浏览器内部运行。如果我们使用 scoped,它将像 Blazor WebAssembly 的 singleton 一样工作。如果想法是将服务范围扩大到当前用户,建议仍然使用 scoped。
要配置范围服务,请使用以下命令:
services.AddScoped<IWeatherForecastService ,WeatherForecastService>();
如果我们有属于用户的数据,我们应该使用 scoped。我们可以通过使用限定范围的对象来保持用户的状态。更多关于 第十一章管理国家。
短暂的
通过使用 transient,每次我们请求时都会创建一个新对象。
要配置临时服务,请使用以下命令:
services.AddTransient<IWeatherForecastService ,WeatherForecastService>();
如果我们不需要保持任何状态,也不介意每次请求创建的对象,那么我们应该使用 transient。
现在我们知道如何配置服务,我们需要通过注入服务来开始使用它。
注入服务
注入服务有三种方式。
我们已经在FetchData
组件代码中看到了第一种方法。我们可以在 Razor 文件中使用@inject
指令:
@inject WeatherForecastService ForecastService
这将确保我们可以访问组件中的WeatherForecastService
。
第二种方法是通过添加Inject
属性来创建属性,如果我们使用代码隐藏的话:
[Inject]
public WeatherForecastService ForecastService { get; set; }
第三种方法是,如果我们想将一个服务注入到另一个服务中,那么我们需要使用构造函数注入服务:
public class MyService
{
public MyService(WeatherForecastService
weatherForecastService)
{
}
}
现在我们知道了 DI 是如何工作的,以及为什么我们应该使用它。
在本章中,我们已经多次提到代码隐藏。在下一节中,我们将了解如何将代码隐藏与 Razor 文件一起使用,甚至完全跳过 Razor 文件。
弄清楚代码放在哪里
我们已经看到了直接在 Razor 文件中编写代码的例子。我更喜欢这样做,除非代码变得太复杂。
有四种方法可以编写组件:
- 在 Razor 文件中
- 在部分班级
- 继承一个类
- 仅代码
在 Razor 文件中
如果我们正在编写的文件没有那么复杂,那么在编写组件时不必切换文件就好了。正如我们在本章中已经介绍过的,我们可以使用@code
指令将代码直接添加到我们的 Razor 文件中。如果我们想将代码移动到代码隐藏文件,那么我们只需要更改指令。对于剩下的代码,我们可以直接进入代码隐藏类。当我开始使用 Blazor 时,在同一个文件中编写代码和标记感觉很奇怪,但我建议您在开发网络应用时尝试一下。
但是许多开发人员更喜欢代码隐藏,将代码与布局分开。为此,我们可以使用分部类。
在部分班级
我们可以创建一个与 Razor 文件同名的分部类,只需添加.cs
。
如果你已经下载了 第三章引入实体框架核心的源代码(或者我们可以在 GitHub 上查看代码),你可以看看MyBlogServerSide
项目中的FetchDataWithCodeBehind.razor.cs
。我已经将所有代码移动到代码隐藏文件中;编译时的结果将与我们将代码保存在 Razor 文件中的结果相同。这只是偏好的问题。
代码隐藏如下所示:
public partial class FetchDataWithCodeBehind
{
[Inject]
public WeatherForecastService ForecastService { get; set; }
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync (DateTime.Now);
}
}
可以看到,我们用的不是@inject
,而是[Inject]
。除此之外,我刚刚从 Razor 文件中复制了代码。
这不是使用代码隐藏文件的唯一方法;我们也可以从代码隐藏文件中继承。
继承一个类
我们也可以创建一个叫完全不同的东西的类(常见的是把它叫做和 Razor 文件一样的东西,最后加上Model
,在我们的 Razor 文件中继承。为了做到这一点,我们需要继承ComponentBase
。在部分类的情况下,类已经从ComponentBase
继承,因为 Razor 文件是这样做的。
任何字段都需要受保护或公开(非私有),以便页面能够访问它们。如果我们不需要从自己的基类继承,我的建议是使用分部类。
这是代码隐藏类声明的一个片段:
public class FetchDataWithInheritsModel:ComponentBase
我们需要从ComponentBase
或者从从ComponentBase
继承的类继承。
在 Razor 文件中,我们将使用@inherits
指令:
@inherits FetchDataWithInheritsModel
Razor 文件现在将继承我们的代码隐藏类(这是第一个可用的创建代码隐藏类的方法)。
部分和继承选项都是将代码移动到代码隐藏文件的简单方法。但是还有另一个选项可以完全跳过 Razor 文件,只使用代码。
仅代码
Razor 文件将在编译时生成代码。如果我们愿意,我们可以跳过 Razor 步骤,完全用代码编写我们的布局。
这个文件(CounterWithoutRazor.cs
)在 GitHub 上有。
计数器示例如下所示:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
namespace MyBlogServerSide.Pages
{
[Route("/CounterWithoutRazor")]
public class CounterWithoutRazor: ComponentBase
{
protected override void BuildRenderTree
(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, "<h1>Counter</h1>\r\n\r\n");
builder.OpenElement(1, "p");
builder.AddContent(2, "Current count: ");
builder.AddContent(3,currentCount);
builder.CloseElement();
builder.AddMarkupContent(4, "\r\n\r\n");
builder.OpenElement(5, "button");
builder.AddAttribute(6, "class", "btn btn-primary");
builder.AddAttribute(7, "onclick", EventCallback.Factory.Create<MouseEventArgs> (this,IncrementCount));
builder.AddContent(8, "Click me");
builder.CloseElement();
}
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
}
Razor 文件将首先被转换成看起来与前面代码大致相同的东西,然后代码被编译。它一个接一个地添加元素,最终呈现出 HTML。
代码中的数字是 Blazor 跟踪渲染树中每个元素的方式。有些人更喜欢像前面的代码块一样编写代码,而不是使用 Razor 语法;社区中甚至有人努力简化手动编写BuildRenderTree()
函数的过程。
我的建议是永远不要手动写这个,但是我一直把它保存在书中,因为它展示了 Razor 文件是如何被编译的。现在我们知道了如何使用代码隐藏,让我们来看看 Blazor 的生命周期事件以及它们何时被执行。
生命周期事件
有几个生命周期事件我们可以用来运行我们的代码。在这一节中,我们将仔细研究它们,看看何时应该使用它们。大多数生命周期事件都有两个版本——同步和异步。
初始化和初始化同步
当组件满载时,调用OnInitialized()
,然后调用OnInitializedAsync()
。这是一个很好的方法来加载任何数据,因为用户界面还没有呈现。如果我们正在做任何长期运行的任务(比如从数据库中获取数据),我们应该把代码放在OnInitializedAsync()
方法中。
如果参数改变,这些方法将不再运行(见OnParameterSet()
和OnParameterSetAsync()
)。
OnParametersSet 和 OnParametersSetAsync
当组件被初始化时(在OnInitialized()
和OnInitializedAsync()
之后),每当我们改变一个参数的值时,调用OnParameterSet()
和OnParameterSetAsync()
。
例如,如果我们在OnInitialized()
方法中加载数据,但它确实使用了一个参数,那么如果参数改变,数据将不会被重新加载,因为OnInitialized()
将只运行一次。当然,我们需要在OnParameterSet()
或OnParameterSetAsync()
中触发数据的重新加载,或者将加载移动到该方法。
OnAfterRender 和 OnAfterRenderAsync
组件完成渲染后,调用OnAfterRender()
和OnAfterRenderAsync()
方法。当方法被调用时,所有的元素都被渲染,所以如果我们想要/需要调用任何 JavaScript 代码,我们必须从这些方法中调用(如果我们试图从任何其他生命周期事件方法中进行 JavaScript 互操作,我们将会得到一个错误)。我们还可以访问一个firstRender
参数,这样我们就可以确保只运行一次初始化代码(只在第一次渲染时)。
应该渲染
当我们的组件被重新渲染时ShouldRender()
被称为,如果它返回false
,那么组件将不再被渲染。它将总是渲染一次;只有在重新呈现时,方法才会运行。
ShouldRender()
没有异步选项。
现在我们知道了不同的生命周期事件何时发生以及以什么顺序发生。一个组件也可以有参数,这样,我们可以重用它们,但是使用不同的数据。
参数
一个参数使得可以向组件发送一个值。要向组件添加参数,我们使用public
属性上的[Parameter]
属性:
@code {
[Parameter]
public string MyParameter { get; set; }
}
我们也可以使用代码隐藏文件来做同样的事情。我们可以使用@page
指令通过在路线中指定来添加参数:
@page "/parameterdemo/{MyParameter}"
在这种情况下,我们必须指定一个与花括号内的名称同名的参数。要在@page
指令中设置参数,我们只需转到网址:/parameterdemo/THEVALUE
。
在某些情况下,我们希望指定另一种类型而不是字符串(字符串是默认值)。我们可以在参数名称后添加数据类型,如下所示:
@page "/parameterdemo/{MyParameter:int}"
只有当数据类型为整数时,这才会匹配路由。我们也可以使用级联参数传递参数。
级联参数
如果我们想将一个值传递给多个组件,我们可以使用级联参数。
不用[Parameter]
,我们可以这样用[CascadingParameter]
:
[CascadingParameter]
public int MyParameter { get; set; }
To pass a value to the component, we surround it with a CascadingValue component like this:
<CascadingValue Value="MyProperty">
<ComponentWithCascadingParameter/>
</CascadingValue> @code {
public string MyProperty { get; set; } = "Test Value";
}
CascadingValue
是我们传递给组件的值,CascadingParameter
是接收该值的属性。
如我们所见,我们没有向ComponentWithCascadingParameter
组件传递任何参数值;级联值将匹配数据类型相同的参数。如果我们有多个相同类型的参数,我们可以用级联参数在组件中指定参数的名称,如下所示:
[CascadingParameter(Name = "MyCascadingParameter")]
我们也可以对通过CascadingValue
的组件这样做,如下所示:
<CascadingValue Value="MyProperty" Name="MyCascadingParameter">
<ComponentWithCascadingParameter/>
</CascadingValue>
如果我们知道值不会改变,我们可以通过使用IsFixed
属性来指定:
<CascadingValue Value="MyProperty" Name="MyCascadingParameter" IsFixed="True">
<ComponentWithCascadingParameter/>
</CascadingValue>
这样,Blazor 就不会寻找变化。级联值/参数不能向上更新,只能向下更新。这意味着要更新级联值,我们需要以另一种方式实现它;从组件内部更新它不会改变层次结构中较高的任何组件。
在 第 5 章创建高级 Blazor 组件中,我们将查看单向解决级联值更新问题的事件。
唷!这是一个信息密集型的章节,但是现在我们知道了 Blazor 组件的基础知识。现在是时候建造一个了!
编写我们的第一个组件
我们将构建的第一个组件显示一个网站上的所有博客文章。平心而论,我们还没有写任何博客文章,但我们会暂时解决这个问题,这样我们就可以开始做一些有趣的事情。
在 第三章引入实体框架核心中,我们创建了一个数据库和一个 API(或接口);现在是使用它们的时候了。
我们首先想看到的是一个博文列表,所以我们希望我们的路线是"/"
。索引页面已经有了这个路径,所以我们要重用这个页面。
要创建我们的第一个组件,请遵循以下说明:
- 在
MyBlogServerSide
项目中,打开Pages/Index.razor
。 -
Replace the contents of that file with the following code:
cs @page "/" @using MyBlog.Data.Interfaces @using MyBlog.Data.Models @inject IMyBlogApi api @code{ protected async Task AddSomePosts() { for (int i = 1; i <= 10; i++) { await api.SaveBlogPostAsync(new BlogPost() { PublishDate = DateTime.Now, Title = $"Blog post {i}", Text = "Text" }); } } }
如果我们从顶部开始,我们可以看到一个页面指令。它将确保在路线为
"/"
时显示组件。然后,我们有三个@using
指令,引入名称空间,这样我们就可以在 Razor 文件中使用它们。然后我们注入我们的应用编程接口(使用数据接口)并将实例命名为api
。在code
部分,有一种方法是在我们的网站上添加 10 篇博文。接下来,我们应该列出博客文章。 -
Add a variable that holds all our posts. In the
code
section, add the following:cs protected List<BlogPost> posts = new List<BlogPost>();
现在我们需要加载数据。
-
To load posts, add the following in the
code
section:cs protected override async Task OnInitializedAsync() { posts = await api.GetBlogPostsAsync(10, 0); await base.OnInitializedAsync(); }
现在,当页面加载时,帖子也会被加载:
10
帖子和页面0
(第一页)。 -
Under the
@inject
row, add the following code:cs <button @onclick="AddSomePosts">Add some fake posts</button> <br /> <br /> <ul> @foreach (var p in posts) { <li>@p.Title</li> } </ul>
我们从增加一个按钮开始,这样我们就可以触发
AddSomePosts
功能。然后我们添加一个无序列表(ul
),在其中,我们循环遍历blogposts
并显示标题。 -
现在我们可以通过按下 Ctrl + F5 ( 调试 | 启动而不调试)来运行应用。
- 出现的页面应该只是显示一个添加一些假帖子按钮。点击此按钮。
- 由于页面不会重新加载,
OnInitializedAsync()
也不会运行。我们需要重新加载我们的网络浏览器来显示数据。在现实世界的应用中,我们不希望用户必须重新加载浏览器,但是由于这一步只是暂时的,我不想让事情过于复杂。
干得好,我们已经创建了第一个组件!
总结
在这一章中,我们学习了很多关于 Razor 语法的知识,我们将在整本书中用到它。我们了解了 DI、指令和参数,当然,也创建了我们的第一个组件。这些知识将帮助我们理解如何创建组件以及如何重用组件。
在下一章中,我们将了解更高级的组件场景。
版权属于:月萌API www.moonapi.com,转载请注明出处