news 2026/5/26 4:02:10

ASP.NET Core Blazor简介和快速入门二(组件基础)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASP.NET Core Blazor简介和快速入门二(组件基础)

.简介

Blazor的生命周期与React组件的生命周期类似,也分为三个阶段:初始化阶段、运行中阶段和销毁阶段,其相关方法有10个,包括设置参数前、初始化、设置参数之后、组件渲染后以及组件的销毁,但是这些方法有些是重复的,只不过是同步与异步的区别。

2.图解

首先将结果图呈现,代码位于第3部分:

1997712-20200405180006291-1493915010

Blazor生命周期方法主要包括:

1

设置参数前

SetParametersAsync

2

初始化

OnInitialized/OnInitializedAsync

3

设置参数后

OnParametersSet/OnParametersSetAsync

4

组件渲染呈现后

OnAfterRender/OnAfterRenderAsync

5

判断是否渲染组件

ShouldRender

6

组件删除前

Dispose

7

通知组件渲染

StateHasChanged

在所有生命周期函数中,有以下需要注意的点:

(1)前5种方法的声明都是virtual,除SetParametersAsync为public外,其他的都是protected。

(2)OnAfterRender/OnAfterRenderAsync方法有一个bool类型的形参firstRender,用于指示是否是第一次渲染(即组件初始化时的渲染)。

(3)同步方法总是先于异步方法执行。

(4)Dispose函数需要通过使用@implements指令实现IDisposable接口来实现。

(5)StateHasChanged无法被重写,可以被显示调用,以便强制实现组件刷新(如果ShouldRender返回true,并且Blazor认为需要刷新);当组件状态更改时不必显示调用此函数,也可导致组件的重新渲染(如果ShouldRender返回true),因为其已经在ComponentBase内部的处理过程(第一次初始化设置参数时、设置参数后和DOM事件处理等)中被调用。

3.代码示例

设置参数时 (SetParametersAsync 设置由组件的父组件在呈现树或路由参数中提供的参数。

每次调用 ParameterView 时,方法的 参数都包含该组件的SetParametersAsync值集。 通过重写 SetParametersAsync 方法,C#代码可以直接与 ParameterView 参数交互。

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {

private string message = "Not set";

[Parameter]

public string? Param { get; set; }

public override async Task SetParametersAsync(ParameterView parameters)

{

if (parameters.TryGetValue<string>(nameof(Param), out var value))

{

if (value is null)

{

message = "The value of 'Param' is null.";

}

else

{

message = $"The value of 'Param' is {value}.";

}

}

await base.SetParametersAsync(parameters);

}

}

组件初始化 (OnInitialized 和 OnInitializedAsync 专门用于在组件实例的整个生命周期内初始化组件。 参数值和参数值更改不应影响在这些方法中执行的初始化。 例如,将静态选项加载到下拉列表中,该下拉列表在组件的生命周期内不会更改,也不依赖于参数值,这是在这些生命周期方法之一中执行的操作。 如果参数值或参数值更改会影响组件状态,请改为使用 OnParametersSet{Async}。

组件在接收 SetParametersAsync 中的初始参数后初始化,此时,将调用这些方法。

如果使用同步父组件初始化,则保证父组件初始化在子组件初始化之前完成。 如果使用异步父组件初始化,则无法确定父组件和子组件初始化的完成顺序,因为它取决于正在运行的初始化代码。

对于同步操作,重写 OnInitialized:

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {

private string? message;

protected override void OnInitialized() =>

message = $"Initialized at {DateTime.Now}";

}

若要执行异步操作,请替代 OnInitializedAsync 并使用 await 运算符:

protected override async Task OnInitializedAsync()

{

//await ...

await Task.Delay(2000); //2秒之后

message = $"Initialized at {DateTime.Now} after 2 second delay";

}

如果自定义基类与自定义初始化逻辑一起使用,需在基类上调用 OnInitializedAsync:

protected override async Task OnInitializedAsync()

{

await ...

await base.OnInitializedAsync();

}

设置参数之后 (OnParametersSet 或 OnParametersSetAsync 在以下情况下调用:

在 OnInitialized 或 OnInitializedAsync 中初始化组件后。

当父组件重新呈现并提供以下内容时:

至少一个参数已更改时的已知或基元不可变类型。

复杂类型的参数。 框架无法知道复杂类型参数的值是否在内部发生了改变,因此,如果存在一个或多个复杂类型的参数,框架始终将参数集视为已更改。

在组件路由中,不能同时对DateTime参数使用datetime路由约束,并将该参数设为可选。 因此,以下 OnParamsSet 组件使用两个 @page 指令来处理具有和没有 URL 中提供的日期段的路由。

@page "/on-params-set"

@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>

Pass a datetime in the URI of the browser's address bar.

For example, add <code>/1-1-2024</code> to the address.

</p>

<p>@message</p>

@code {

private string? message;

[Parameter]

public DateTime StartDate { get; set; }

protected override void OnParametersSet()

{

if (StartDate == default)

{

StartDate = DateTime.Now;

message = $"No start date in URL. Default value applied " +

$"(StartDate: {StartDate}).";

}

else

{

message = $"The start date in the URL was used " +

$"(StartDate: {StartDate}).";

}

}

}

应用参数和属性值时,异步操作必须在 OnParametersSetAsync 生命周期事件期间发生:

protected override async Task OnParametersSetAsync()

{

await ...

}

如果自定义基类与自定义初始化逻辑一起使用,需在基类上调用 OnParametersSetAsync:

protected override async Task OnParametersSetAsync()

{

await ...

await base.OnParametersSetAsync();

}

组件呈现之后 (OnAfterRender 和 OnAfterRenderAsync 在组件以交互方式呈现并且 UI 完成更新之后被调用(例如,元素添加到浏览器 DOM 之后)。 此时会填充元素和组件引用。 在此阶段中,可使用呈现的内容执行其他初始化步骤,例如与呈现的 DOM 元素交互的 JS 互操作调用。

这些方法不会在预呈现或静态服务器端渲染(静态 SSR)期间在服务器上调用,因为这些进程未附加到实时浏览器 DOM,并且已在 DOM 更新之前完成。

对于 OnAfterRenderAsync,组件在任何返回 Task 的操作完成后不会自动重渲染,以避免无限渲染循环。

firstRender 和 OnAfterRender 的 OnAfterRenderAsync 参数:

在第一次呈现组件实例时设置为 true。

可用于确保初始化操作仅执行一次。

@page "/after-render"

@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>

<button @onclick="HandleClick">Log information (and trigger a render)</button>

</p>

<p>Study logged messages in the console.</p>

@code {

protected override void OnAfterRender(bool firstRender) =>

Logger.LogInformation("firstRender = {FirstRender}", firstRender);

private void HandleClick() => Logger.LogInformation("HandleClick called");

}

加载页面并选择按钮时,AfterRender.razor 示例向控制台输出以下内容:

在渲染后立即进行的异步工作必须在 OnAfterRenderAsync 生命周期事件期间发生:

protected override async Task OnAfterRenderAsync(bool firstRender)

{

...

}

如果自定义基类与自定义初始化逻辑一起使用,需在基类上调用 OnAfterRenderAsync:

protected override async Task OnAfterRenderAsync(bool firstRender)

{

...

await base.OnAfterRenderAsync(firstRender);

}

基类生命周期方法

重写 Blazor 的生命周期方法时,无需为 ComponentBase 调用基类生命周期方法。 但在以下情况下,组件应调用重写的基类生命周期方法:

重写 ComponentBase.SetParametersAsync 时,通常会调用 await base.SetParametersAsync(parameters);, 因为基类方法会调用其他生命周期方法并以复杂的方式触发渲染。 有关详细信息,请参阅设置参数时 (SetParametersAsync) 部分。

如果基类方法包含必须执行的逻辑。 库使用者通常在继承基类时调用基类生命周期方法,因为库基类通常具有要执行的自定义生命周期逻辑。 如果应用使用某个库中的基类,请参阅该库的文档以获取指导。

以下示例中调用了 base.OnInitialized(); 以确保会执行基类的 OnInitialized 方法。 如果没有调用,BlazorRocksBase2.OnInitialized 不会执行。

@page "/blazor-rocks-2"

@inherits BlazorRocksBase2

@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>

@BlazorRocksText

</p>

@code {

protected override void OnInitialized()

{

Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

base.OnInitialized();

}

}

using Microsoft.AspNetCore.Components;

namespace BlazorAppWasm

{

public class BlazorRocksBase2: ComponentBase

{

[Inject]

private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

protected override void OnInitialized() =>

Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");

}

}

2、数据绑定

Blazor提供了强大的数据绑定机制,主要包括单向绑定和双向绑定两种模式。

1. 单向数据绑定

单向绑定是指数据从组件流向UI,但UI的变化不会自动更新数据源。

基本语法

<!-- 使用 @ 符号进行单向绑定 -->

<p>当前值: @currentValue</p>

<span>用户名: @UserName</span>

<div>创建时间: @CreateTime.ToString("yyyy-MM-dd")</div>

完整示例

<!-- OneWayBinding.razor -->

<div class="one-way-demo">

<h3>单向绑定示例</h3>

<!-- 显示数据,但不允许编辑 -->

<div class="display-area">

<p>计数器: <strong>@count</strong></p>

<p>消息: <strong>@message</strong></p>

<p>用户信息: <strong>@user.Name</strong> - <strong>@user.Age</strong>岁</p>

</div>

<!-- 控制按钮 -->

<div class="control-area">

<button @onclick="Increment" class="btn btn-primary">增加计数</button>

<button @onclick="ChangeMessage" class="btn btn-secondary">更改消息</button>

<button @onclick="UpdateUser" class="btn btn-info">更新用户</button>

</div>

</div>

@code {

private int count = 0;

private string message = "初始消息";

private User user = new User { Name = "张三", Age = 25 };

private void Increment()

{

count++;

// StateHasChanged(); // 通常不需要手动调用,事件处理会自动触发重新渲染

}

private void ChangeMessage()

{

message = $"消息已更新: {DateTime.Now:HH:mm:ss}";

}

private void UpdateUser()

{

user = new User { Name = "李四", Age = 30 };

}

class User

{

public string Name { get; set; } = string.Empty;

public int Age { get; set; }

}

}

2. 双向数据绑定

双向绑定允许数据在组件和UI之间双向流动:UI变化自动更新数据源,数据源变化自动更新UI。

基本语法

<!-- 使用 @bind 指令进行双向绑定 -->

<input @bind="propertyName" />

<input @bind="fieldName" />

<select @bind="selectedValue">...</select>

完整示例

<!-- TwoWayBinding.razor -->

<div class="two-way-demo">

<h3>双向绑定示例</h3>

<div class="form-group">

<label>用户名:</label>

<input @bind="userName" class="form-control" />

<small>显示: @userName</small>

</div>

<div class="form-group">

<label>邮箱:</label>

<input @bind="email" class="form-control" />

<small>显示: @email</small>

</div>

<div class="form-group">

<label>年龄:</label>

<input @bind="age" type="number" class="form-control" />

<small>显示: @age</small>

</div>

<div class="form-group">

<label>城市:</label>

<select @bind="selectedCity" class="form-control">

<option value="">请选择</option>

<option value="Beijing">北京</option>

<option value="Shanghai">上海</option>

<option value="Guangzhou">广州</option>

<option value="Shenzhen">深圳</option>

</select>

<small>选择: @selectedCity</small>

</div>

<div class="form-group">

<label>是否同意协议:</label>

<input type="checkbox" @bind="isAgreed" />

<span>@(isAgreed ? "已同意" : "未同意")</span>

</div>

<!-- 显示汇总信息 -->

<div class="summary">

<h4>汇总信息:</h4>

<p>用户名: @userName</p>

<p>邮箱: @email</p>

<p>年龄: @age</p>

<p>城市: @selectedCity</p>

<p>同意协议: @isAgreed</p>

</div>

</div>

@code {

private string userName = string.Empty;

private string email = string.Empty;

private int age = 0;

private string selectedCity = string.Empty;

private bool isAgreed = false;

}

3. 绑定事件控制

3.1 绑定特定事件

默认情况下,@bind 在失去焦点时更新。可以使用 @bind:event 指定触发事件:

<!-- 实时绑定(输入时立即更新) -->

<div class="real-time-demo">

<h4>实时绑定示例</h4>

<input @bind="searchText" @bind:event="oninput"

placeholder="输入搜索内容..." />

<p>实时搜索: @searchText</p>

<!-- 对比默认行为 -->

<input @bind="normalText" placeholder="默认绑定(失去焦点更新)" />

<p>默认绑定: @normalText</p>

</div>

@code {

private string searchText = string.Empty;

private string normalText = string.Empty;

}

3.2 绑定格式化

<div class="format-demo">

<h4>格式化绑定示例</h4>

<!-- 日期格式化 -->

<input @bind="startDate" @bind:format="yyyy-MM-dd" type="date" />

<p>选择的日期: @startDate.ToString("yyyy年MM月dd日")</p>

<!-- 数字格式化 -->

<input @bind="price" @bind:format="F2" type="number" step="0.01" />

<p>价格: @price.ToString("C")</p>

</div>

@code {

private DateTime startDate = DateTime.Today;

private decimal price = 0.00m;

}

4. 自定义组件双向绑定

在自定义组件中实现双向绑定:

子组件

<!-- CustomInput.razor -->

<div class="custom-input">

<label>@Label</label>

<input

value="@Value"

@oninput="HandleInput"

class="form-control @AdditionalClass"

placeholder="@Placeholder" />

@if (!string.IsNullOrEmpty(ValidationMessage))

{

<div class="text-danger">@ValidationMessage</div>

}

</div>

@code {

[Parameter]

public string Value { get; set; } = string.Empty;

[Parameter]

public EventCallback<string> ValueChanged { get; set; }

[Parameter]

public string Label { get; set; } = string.Empty;

[Parameter]

public string Placeholder { get; set; } = string.Empty;

[Parameter]

public string AdditionalClass { get; set; } = string.Empty;

[Parameter]

public string ValidationMessage { get; set; } = string.Empty;

private async Task HandleInput(ChangeEventArgs e)

{

Value = e.Value?.ToString() ?? string.Empty;

await ValueChanged.InvokeAsync(Value);

}

}

父组件使用

<!-- ParentComponent.razor -->

<div class="parent-demo">

<h3>自定义组件双向绑定</h3>

<CustomInput

@bind-Value="userName"

Label="用户名"

Placeholder="请输入用户名" />

<CustomInput

@bind-Value="email"

Label="邮箱"

Placeholder="请输入邮箱地址"

ValidationMessage="@(IsValidEmail ? "" : "邮箱格式不正确")" />

<div class="result">

<p>用户名: @userName</p>

<p>邮箱: @email</p>

</div>

</div>

@code {

private string userName = string.Empty;

private string email = string.Empty;

private bool IsValidEmail => email.Contains("@") && email.Contains(".");

}

5.复杂对象绑定

<!-- ComplexObjectBinding.razor -->

<div class="complex-binding">

<h3>复杂对象绑定</h3>

<div class="form-section">

<h4>用户信息</h4>

<div class="form-group">

<label>姓名:</label>

<input @bind="currentUser.Name" class="form-control" />

</div>

<div class="form-group">

<label>年龄:</label>

<input @bind="currentUser.Age" type="number" class="form-control" />

</div>

<div class="form-group">

<label>地址:</label>

<input @bind="currentUser.Address.Street" class="form-control" placeholder="街道" />

<input @bind="currentUser.Address.City" class="form-control" placeholder="城市" />

</div>

</div>

<div class="display-section">

<h4>当前用户信息:</h4>

<pre>@userInfoJson</pre>

</div>

<button @onclick="ResetUser" class="btn btn-warning">重置用户</button>

<button @onclick="CreateNewUser" class="btn btn-success">创建新用户</button>

</div>

@code {

private User currentUser = new User();

private string userInfoJson =>

System.Text.Json.JsonSerializer.Serialize(currentUser, new System.Text.Json.JsonSerializerOptions

{

WriteIndented = true

});

private void ResetUser()

{

currentUser = new User();

}

private void CreateNewUser()

{

currentUser = new User

{

Name = "新用户",

Age = 18,

Address = new Address { Street = "新建街道", City = "新建城市" }

};

}

class User

{

public string Name { get; set; } = string.Empty;

public int Age { get; set; }

public Address Address { get; set; } = new Address();

}

class Address

{

public string Street { get; set; } = string.Empty;

public string City { get; set; } = string.Empty;

}

}

6.绑定模式对比

绑定类型

语法

更新时机

适用场景

单向绑定

@property

数据源变化时

显示数据、计算属性

双向绑定

@bind="property"

失去焦点时

表单输入、用户交互

实时双向

@bind="property" @bind:event="oninput"

输入时实时更新

搜索框、实时验证

自定义绑定

@bind-Value="property"

自定义事件触发

自定义表单组件

3、事件处理

1. 基本事件处理

1.1 单击事件

<!-- ClickEvents.razor -->

<div class="click-demo">

<h3>单击事件示例</h3>

<!-- 基本点击事件 -->

<button @onclick="HandleClick" class="btn btn-primary">

点击我

</button>

<!-- 带参数的事件处理 -->

<div class="button-group">

<button @onclick="() => HandleButtonClick(1)" class="btn btn-secondary">按钮 1</button>

<button @onclick="() => HandleButtonClick(2)" class="btn btn-secondary">按钮 2</button>

<button @onclick="() => HandleButtonClick(3)" class="btn btn-secondary">按钮 3</button>

</div>

<!-- 显示点击结果 -->

<div class="result">

<p>最后点击的按钮: @lastClickedButton</p>

<p>点击次数: @clickCount</p>

</div>

</div>

@code {

private int lastClickedButton = 0;

private int clickCount = 0;

private void HandleClick()

{

clickCount++;

Console.WriteLine("按钮被点击了!");

}

private void HandleButtonClick(int buttonNumber)

{

lastClickedButton = buttonNumber;

clickCount++;

StateHasChanged();

}

}

1.2 异步事件处理

<!-- AsyncEvents.razor -->

<div class="async-demo">

<h3>异步事件处理</h3>

<button @onclick="HandleAsyncClick" class="btn btn-primary" disabled="@isLoading">

@if (isLoading)

{

<span>加载中...</span>

}

else

{

<span>模拟异步操作</span>

}

</button>

<div class="result">

<p>操作结果: @operationResult</p>

<p>耗时: @elapsedTime 毫秒</p>

</div>

</div>

@code {

private bool isLoading = false;

private string operationResult = string.Empty;

private long elapsedTime = 0;

private async Task HandleAsyncClick()

{

isLoading = true;

operationResult = "操作开始...";

var stopwatch = System.Diagnostics.Stopwatch.StartNew();

// 模拟异步操作

await Task.Delay(2000);

stopwatch.Stop();

elapsedTime = stopwatch.ElapsedMilliseconds;

operationResult = $"操作完成!数据已保存。";

isLoading = false;

StateHasChanged();

}

}

2. 表单事件处理

2.1 输入事件

<!-- FormEvents.razor -->

<div class="form-events">

<h3>表单事件处理</h3>

<div class="form-group">

<label>输入文本:</label>

<input @oninput="HandleInput"

@onchange="HandleChange"

class="form-control"

placeholder="输入内容..." />

<small>实时输入: @inputValue | 变化事件: @changeValue</small>

</div>

<div class="form-group">

<label>选择选项:</label>

<select @onchange="HandleSelectChange" class="form-control">

<option value="">请选择</option>

<option value="option1">选项一</option>

<option value="option2">选项二</option>

<option value="option3">选项三</option>

</select>

<small>选择的值: @selectedValue</small>

</div>

<div class="form-group">

<label>

<input type="checkbox" @onchange="HandleCheckboxChange" />

同意条款

</label>

<small>状态: @(isChecked ? "已选中" : "未选中")</small>

</div>

<!-- 表单提交 -->

<form @onsubmit="HandleSubmit" @onvalidSubmit="HandleValidSubmit">

<div class="form-group">

<label>用户名:</label>

<input @bind="user.Username" class="form-control" required />

</div>

<div class="form-group">

<label>邮箱:</label>

<input @bind="user.Email" type="email" class="form-control" required />

</div>

<button type="submit" class="btn btn-success">提交表单</button>

</form>

<div class="form-result">

<h4>表单数据:</h4>

<pre>@System.Text.Json.JsonSerializer.Serialize(user, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })</pre>

<p>提交状态: @submitStatus</p>

</div>

</div>

@code {

private string inputValue = string.Empty;

private string changeValue = string.Empty;

private string selectedValue = string.Empty;

private bool isChecked = false;

private string submitStatus = "未提交";

private User user = new User();

private void HandleInput(ChangeEventArgs e)

{

inputValue = e.Value?.ToString() ?? string.Empty;

}

private void HandleChange(ChangeEventArgs e)

{

changeValue = e.Value?.ToString() ?? string.Empty;

}

private void HandleSelectChange(ChangeEventArgs e)

{

selectedValue = e.Value?.ToString() ?? string.Empty;

}

private void HandleCheckboxChange(ChangeEventArgs e)

{

isChecked = (bool)(e.Value ?? false);

}

private void HandleSubmit()

{

submitStatus = "表单提交(可能有验证错误)";

}

private void HandleValidSubmit()

{

submitStatus = $"表单验证通过!数据已保存 - {DateTime.Now:HH:mm:ss}";

// 这里可以调用API保存数据

}

class User

{

public string Username { get; set; } = string.Empty;

public string Email { get; set; } = string.Empty;

}

}

3. 鼠标和键盘事件

3.1 鼠标事件

<!-- MouseEvents.razor -->

<div class="mouse-events">

<h3>鼠标事件</h3>

<div class="interactive-area"

@onmousedown="HandleMouseDown"

@onmouseup="HandleMouseUp"

@onmousemove="HandleMouseMove"

@onmouseover="HandleMouseOver"

@onmouseout="HandleMouseOut"

@onclick="HandleAreaClick"

@ondblclick="HandleDoubleClick"

style="width: 300px; height: 200px; border: 2px solid #007bff; padding: 20px; margin: 10px 0;">

鼠标交互区域

</div>

<div class="event-log">

<h4>事件日志:</h4>

<ul>

@foreach (var log in eventLogs.TakeLast(10).Reverse())

{

<li>@log</li>

}

</ul>

</div>

<div class="mouse-info">

<p>鼠标位置: (@mouseX, @mouseY)</p>

<p>按钮状态: @(isMouseDown ? "按下" : "释放")</p>

<p>悬停状态: @(isMouseOver ? "在区域内" : "在区域外")</p>

</div>

</div>

@code {

private double mouseX = 0;

private double mouseY = 0;

private bool isMouseDown = false;

private bool isMouseOver = false;

private List<string> eventLogs = new List<string>();

private void LogEvent(string eventName)

{

eventLogs.Add($"{DateTime.Now:HH:mm:ss.fff} - {eventName}");

StateHasChanged();

}

private void HandleMouseDown(MouseEventArgs e)

{

isMouseDown = true;

LogEvent($"MouseDown - 按钮: {e.Button}, 位置: ({e.ClientX}, {e.ClientY})");

}

private void HandleMouseUp(MouseEventArgs e)

{

isMouseDown = false;

LogEvent($"MouseUp - 按钮: {e.Button}, 位置: ({e.ClientX}, {e.ClientY})");

}

private void HandleMouseMove(MouseEventArgs e)

{

mouseX = e.ClientX;

mouseY = e.ClientY;

// 注意:频繁触发,生产环境需要节流

// LogEvent($"MouseMove - 位置: ({e.ClientX}, {e.ClientY})");

}

private void HandleMouseOver(MouseEventArgs e)

{

isMouseOver = true;

LogEvent("MouseOver");

}

private void HandleMouseOut(MouseEventArgs e)

{

isMouseOver = false;

LogEvent("MouseOut");

}

private void HandleAreaClick(MouseEventArgs e)

{

LogEvent($"Click - 按钮: {e.Button}");

}

private void HandleDoubleClick(MouseEventArgs e)

{

LogEvent($"DoubleClick - 按钮: {e.Button}");

}

}

3.2 键盘事件

<!-- KeyboardEvents.razor -->

<div class="keyboard-events">

<h3>键盘事件</h3>

<div class="input-area">

<input @onkeydown="HandleKeyDown"

@onkeyup="HandleKeyUp"

@onkeypress="HandleKeyPress"

class="form-control"

placeholder="在这里输入并观察键盘事件..." />

</div>

<div class="event-log">

<h4>键盘事件日志:</h4>

<ul>

@foreach (var log in keyEventLogs.TakeLast(10).Reverse())

{

<li>@log</li>

}

</ul>

</div>

<div class="key-info">

<p>最后按下的键: @lastKey</p>

<p>Ctrl 按下: @(isCtrlPressed ? "是" : "否")</p>

<p>Shift 按下: @(isShiftPressed ? "是" : "否")</p>

<p>Alt 按下: @(isAltPressed ? "是" : "否")</p>

</div>

</div>

@code {

private string lastKey = "无";

private bool isCtrlPressed = false;

private bool isShiftPressed = false;

private bool isAltPressed = false;

private List<string> keyEventLogs = new List<string>();

private void LogKeyEvent(string eventName, KeyboardEventArgs e)

{

var log = $"{DateTime.Now:HH:mm:ss.fff} - {eventName}: Key='{e.Key}', Code='{e.Code}'";

if (e.CtrlKey) log += " [Ctrl]";

if (e.ShiftKey) log += " [Shift]";

if (e.AltKey) log += " [Alt]";

keyEventLogs.Add(log);

StateHasChanged();

}

private void HandleKeyDown(KeyboardEventArgs e)

{

lastKey = e.Key;

isCtrlPressed = e.CtrlKey;

isShiftPressed = e.ShiftKey;

isAltPressed = e.AltKey;

LogKeyEvent("KeyDown", e);

// 快捷键处理示例

if (e.CtrlKey && e.Key == "s")

{

e.PreventDefault(); // 阻止浏览器默认保存行为

LogKeyEvent("快捷键: Ctrl+S", e);

}

}

private void HandleKeyUp(KeyboardEventArgs e)

{

isCtrlPressed = e.CtrlKey;

isShiftPressed = e.ShiftKey;

isAltPressed = e.AltKey;

LogKeyEvent("KeyUp", e);

}

private void HandleKeyPress(KeyboardEventArgs e)

{

LogKeyEvent("KeyPress", e);

}

}

4. 焦点和剪贴板事件

<!-- FocusClipboardEvents.razor -->

<div class="focus-clipboard">

<h3>焦点和剪贴板事件</h3>

<div class="form-group">

<label>焦点测试输入框:</label>

<input @onfocus="HandleFocus"

@onblur="HandleBlur"

class="form-control"

placeholder="点击获取焦点,点击别处失去焦点" />

</div>

<div class="form-group">

<label>复制粘贴测试:</label>

<textarea @oncopy="HandleCopy"

@oncut="HandleCut"

@onpaste="HandlePaste"

class="form-control"

rows="3"

placeholder="在这里测试复制、剪切、粘贴操作">这是一些测试文本</textarea>

</div>

<div class="event-log">

<h4>事件状态:</h4>

<p>焦点状态: <span class="@(hasFocus ? "text-success" : "text-danger")">@(hasFocus ? "有焦点" : "无焦点")</span></p>

<p>最后操作: @lastOperation</p>

<p>剪贴板内容: @clipboardContent</p>

</div>

</div>

@code {

private bool hasFocus = false;

private string lastOperation = "无";

private string clipboardContent = "无";

private void HandleFocus(FocusEventArgs e)

{

hasFocus = true;

lastOperation = "获得焦点";

StateHasChanged();

}

private void HandleBlur(FocusEventArgs e)

{

hasFocus = false;

lastOperation = "失去焦点";

StateHasChanged();

}

private void HandleCopy(ClipboardEventArgs e)

{

lastOperation = "复制操作";

clipboardContent = "复制的内容无法直接获取(安全限制)";

StateHasChanged();

}

private void HandleCut(ClipboardEventArgs e)

{

lastOperation = "剪切操作";

clipboardContent = "剪切的内容无法直接获取(安全限制)";

StateHasChanged();

}

private void HandlePaste(ClipboardEventArgs e)

{

lastOperation = "粘贴操作";

clipboardContent = "粘贴的内容无法直接获取(安全限制)";

StateHasChanged();

}

}

5. 自定义事件处理

5.1 事件参数封装

<!-- CustomEventHandling.razor -->

<div class="custom-events">

<h3>自定义事件处理</h3>

<!-- 事件冒泡和阻止默认行为 -->

<div @onclick="HandleParentClick" style="padding: 20px; border: 2px solid red;">

<p>父级区域(点击会触发)</p>

<button @onclick="HandleChildClick"

@onclick:stopPropagation

class="btn btn-primary">

子按钮(点击不会冒泡)

</button>

<button @onclick="HandleChildClickWithPrevent"

@onclick:preventDefault

class="btn btn-secondary">

阻止默认行为的按钮

</button>

</div>

<!-- 自定义事件处理逻辑 -->

<div class="custom-actions">

<h4>自定义操作:</h4>

<button @onclick="HandleCustomAction1" class="btn btn-info">操作1</button>

<button @onclick="HandleCustomAction2" class="btn btn-info">操作2</button>

<button @onclick="async () => await HandleCustomAsyncAction()" class="btn btn-info">异步操作</button>

</div>

<div class="action-log">

<h4>操作日志:</h4>

<ul>

@foreach (var log in actionLogs.TakeLast(5).Reverse())

{

<li>@log</li>

}

</ul>

</div>

</div>

@code {

private List<string> actionLogs = new List<string>();

private void LogAction(string action)

{

actionLogs.Add($"{DateTime.Now:HH:mm:ss} - {action}");

StateHasChanged();

}

private void HandleParentClick()

{

LogAction("父级区域被点击");

}

private void HandleChildClick()

{

LogAction("子按钮被点击(事件不会冒泡)");

}

private void HandleChildClickWithPrevent()

{

LogAction("阻止默认行为的按钮被点击");

}

private void HandleCustomAction1()

{

LogAction("执行自定义操作1");

// 自定义业务逻辑

}

private void HandleCustomAction2(MouseEventArgs e)

{

LogAction($"执行自定义操作2 - 点击位置: ({e.ClientX}, {e.ClientY})");

// 自定义业务逻辑

}

private async Task HandleCustomAsyncAction()

{

LogAction("开始异步操作");

await Task.Delay(1000);

LogAction("异步操作完成");

}

}

6. 事件处理最佳实践

6.1 性能优化

<!-- OptimizedEvents.razor -->

<div class="optimized-events">

<h3>事件处理性能优化</h3>

<!-- 避免内联Lambda表达式(可能引起不必要的重渲染) -->

@foreach (var item in items)

{

<div class="item" @key="item.Id">

<span>@item.Name</span>

<!-- 好的做法:使用方法引用 -->

<button @onclick="() => DeleteItem(item.Id)" class="btn btn-sm btn-danger">删除</button>

</div>

}

<!-- 大量事件考虑使用事件委托 -->

<div class="large-list">

@foreach (var item in largeList)

{

<div class="list-item" data-id="@item.Id" data-name="@item.Name" @onclick="(e) => HandleListItemClick(e, item.Id)">

@item.Name

</div>

}

</div>

<div class="action-log">

<h4>操作日志:</h4>

<ul>

@foreach (var log in actionLogs.TakeLast(5).Reverse())

{

<li>@log</li>

}

</ul>

</div>

</div>

@code {

private List<Item> items = new List<Item>

{

new Item { Id = 1, Name = "项目1" },

new Item { Id = 2, Name = "项目2" },

new Item { Id = 3, Name = "项目3" }

};

private List<Item> largeList = Enumerable.Range(1, 100)

.Select(i => new Item { Id = i, Name = $"项目{i}" })

.ToList();

private List<string> actionLogs = new List<string>();

private void DeleteItem(int id)

{

items.RemoveAll(i => i.Id == id);

LogAction($"删除了项目 {id}");

}

private void HandleListItemClick(MouseEventArgs e, int itemId)

{

// 通过参数 itemId 就知道是哪个按钮被点击了

Console.WriteLine($"Clicked item ID: {itemId}");

}

// 添加 LogAction 方法

private void LogAction(string action)

{

actionLogs.Add($"{DateTime.Now:HH:mm:ss} - {action}");

StateHasChanged();

}

class Item

{

public int Id { get; set; }

public string Name { get; set; } = string.Empty;

}

}

7. 常用事件总结

事件类型

指令

事件参数

说明

点击事件

@onclick

MouseEventArgs

鼠标点击

双击事件

@ondblclick

MouseEventArgs

鼠标双击

鼠标移动

@onmousemove

MouseEventArgs

鼠标移动

鼠标按下

@onmousedown

MouseEventArgs

鼠标按下

鼠标释放

@onmouseup

MouseEventArgs

鼠标释放

键盘按下

@onkeydown

KeyboardEventArgs

键盘按下

键盘释放

@onkeyup

KeyboardEventArgs

键盘释放

输入事件

@oninput

ChangeEventArgs

输入时触发

变化事件

@onchange

ChangeEventArgs

值变化时触发

获得焦点

@onfocus

FocusEventArgs

元素获得焦点

失去焦点

@onblur

FocusEventArgs

元素失去焦点

表单提交

@onsubmit

EventArgs

表单提交

4、组件参数和级联参数

1. 组件参数(Parameter)

参数主要用来在各组件之间传递值,在初始项目的SurveyPrompt组件中就包含了一个参数:

[Parameter]

public string Title { get; set; }

通过用Parameter修饰符来修饰,就可以将指定的属性(注意要是public的)声明为参数,使用也很简单:

<SurveyPrompt Title="这里是参数的值" />

2. CaptureUnmatchedValues

是 Blazor 中一个非常有用的特性,它允许组件捕获所有未匹配到组件参数的额外属性。

基本概念

当你在组件上设置了属性,但这些属性没有对应的 时,可以捕获这些"未匹配"的属性,而且修饰的属性必须要是字典类型:IDictionary<string,object>。

基本用法

<!-- MyComponent.razor -->

<div @attributes="AdditionalAttributes">

组件内容

</div>

@code {

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyComponent.razor -->

<div @attributes="AdditionalAttributes">

组件内容

</div>

@code {

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyComponent.razor -->

<div @attributes="AdditionalAttributes">

组件内容

</div>

@code {

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyComponent.razor -->

<div @attributes="AdditionalAttributes">

组件内容

</div>

@code {

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

使用场景示例

1. 创建可复用的按钮组件

<!-- MyButton.razor -->

<button @attributes="AdditionalAttributes" class="btn @Class">

@ChildContent

</button>

@code {

[Parameter]

public string Class { get; set; } = string.Empty;

[Parameter]

public RenderFragment? ChildContent { get; set; }

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyButton.razor -->

<button @attributes="AdditionalAttributes" class="btn @Class">

@ChildContent

</button>

@code {

[Parameter]

public string Class { get; set; } = string.Empty;

[Parameter]

public RenderFragment? ChildContent { get; set; }

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyButton.razor -->

<button @attributes="AdditionalAttributes" class="btn @Class">

@ChildContent

</button>

@code {

[Parameter]

public string Class { get; set; } = string.Empty;

[Parameter]

public RenderFragment? ChildContent { get; set; }

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- MyButton.razor -->

<button @attributes="AdditionalAttributes" class="btn @Class">

@ChildContent

</button>

@code {

[Parameter]

public string Class { get; set; } = string.Empty;

[Parameter]

public RenderFragment? ChildContent { get; set; }

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

使用方式

<MyButton class="btn-primary"

id="submit-btn"

onclick="console.log('clicked me')"

data-custom="value">

点击我

</MyButton>

<MyButton class="btn-primary"

id="submit-btn"

onclick="console.log('clicked me')"

data-custom="value">

点击我

</MyButton>

<MyButton class="btn-primary"

id="submit-btn"

onclick="console.log('clicked me')"

data-custom="value">

点击我

</MyButton>

<MyButton class="btn-primary"

id="submit-btn"

onclick="console.log('clicked me')"

data-custom="value">

点击我

</MyButton>

2.包装第三方组件

<!-- WrapperComponent.razor -->

<ThirdPartyComponent @attributes="AdditionalAttributes"

SpecificParameter="@SpecificValue" />

@code {

[Parameter]

public string SpecificValue { get; set; } = string.Empty;

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- WrapperComponent.razor -->

<ThirdPartyComponent @attributes="AdditionalAttributes"

SpecificParameter="@SpecificValue" />

@code {

[Parameter]

public string SpecificValue { get; set; } = string.Empty;

[Parameter(CaptureUnmatchedValues = true)]

public Dictionary<string, object> AdditionalAttributes { get; set; } =

new Dictionary<string, object>();

}

<!-- WrapperComponent.razor -->

<ThirdPartyComponent @attributes="AdditionalAttributes"

SpecificParameter="@SpecificValue" />

@code {

[Parameter]

public string SpecificValue { get; set; } = string.Empty;

[Parameter(CaptureUnmatchedValues = true)]

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 7:54:53

Obsidian主页定制终极指南:打造你的专属知识管理门户

还在为Obsidian杂乱无章的主页而烦恼吗&#xff1f;想要一个既美观又实用的知识管理门户吗&#xff1f;今天就来教你如何通过Farouks Homepage主题&#xff0c;快速打造个人专属的Obsidian主页。这个主题专为知识管理而设计&#xff0c;集成了卡片布局、动态进度条和智能倒计时…

作者头像 李华
网站建设 2026/5/25 18:45:09

三步完成InternLM3模型4bit量化:显存直降50%的终极部署指南

三步完成InternLM3模型4bit量化&#xff1a;显存直降50%的终极部署指南 【免费下载链接】InternLM Official release of InternLM series (InternLM, InternLM2, InternLM2.5, InternLM3). 项目地址: https://gitcode.com/gh_mirrors/in/InternLM 还在为AI大模型部署时爆…

作者头像 李华
网站建设 2026/5/25 22:13:34

fcitx5 vs ibus:中文输入法性能深度对比

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个输入法性能测试工具&#xff0c;功能包括&#xff1a;1. 测量输入法启动时间 2. 记录输入响应延迟 3. 统计内存和CPU占用 4. 测试词库加载速度 5. 生成可视化对比报告。要求…

作者头像 李华
网站建设 2026/5/26 7:05:20

智能简历解析终极指南:如何用AI技术精准提取关键信息

智能简历解析终极指南&#xff1a;如何用AI技术精准提取关键信息 【免费下载链接】Resume-Matcher Resume Matcher is an open source, free tool to improve your resume. It works by using language models to compare and rank resumes with job descriptions. 项目地址…

作者头像 李华
网站建设 2026/5/25 16:35:56

springAI学习 一

一、Spring AI 概述 什么是Spring AI&#xff1f; Spring生态的AI集成框架 统一API访问不同AI服务&#xff08;OpenAI、Azure OpenAI、Anthropic等&#xff09; 支持多种AI功能&#xff1a;聊天、文生图、嵌入、向量存储等 Spring AI 是一个用于 AI 工程的应用框架。 其目标…

作者头像 李华
网站建设 2026/5/26 7:03:43

串口助手唐老鸭版:解决你串口调试痛点的终极方案

串口助手唐老鸭版&#xff1a;解决你串口调试痛点的终极方案 【免费下载链接】串口助手唐老鸭版使用说明 串口助手(唐老鸭版)是一款功能强大且易于使用的串口调试工具&#xff0c;专为开发者设计。其界面友好&#xff0c;操作简单&#xff0c;能够满足各种串口调试需求。无论是…

作者头像 李华