颐矩位拾大家好,我是码农刚子。上一章介绍了Blazor的简介,开发工具及环境,基本语法和一些示例。接下来我们继续了解Blazor 组件相关的基础知识,希望对你有所帮助。
1、组件生命周期
1.简介
Blazor的生命周期与React组件的生命周期类似,也分为三个阶段:初始化阶段、运行中阶段和销毁阶段,其相关方法有10个,包括设置参数前、初始化、设置参数之后、组件渲染后以及组件的销毁,但是这些方法有些是重复的,只不过是同步与异步的区别。
2.图解
首先将结果图呈现,代码位于第3部分:
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?}"
Set Parameters Async
Set Parameters Async Example
@message
@code {
private string message = "Not set";
Parameter
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue(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"
On Initialized
On Initialized Example
@message
@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}"
On Parameters Set
On Parameters Set Example
Pass a datetime in the URI of the browser's address bar.
For example, add /1-1-2024 to the address.
@message
@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 Logger
After Render
After Render Example
Log information (and trigger a render)
Study logged messages in the console.
@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 Logger
Blazor Rocks!
Blazor Rocks! Example 2
@BlazorRocksText
@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 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提供了强大的数据绑定机制,主要包括单向绑定和双向绑定两种模式。
- 单向数据绑定
单向绑定是指数据从组件流向UI,但UI的变化不会自动更新数据源。
基本语法
当前值: @currentValue
用户名: @UserName
创建时间: @CreateTime.ToString("yyyy-MM-dd")
完整示例
单向绑定示例
计数器: @count
消息: @message
用户信息: @user.Name - @user.Age岁
增加计数
更改消息
更新用户
@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; }
}
}
- 双向数据绑定
双向绑定允许数据在组件和UI之间双向流动:UI变化自动更新数据源,数据源变化自动更新UI。
基本语法
- ...
- 完整示例
双向绑定示例
用户名:
显示: @userName
邮箱:
显示: @email
年龄:
显示: @age
城市:
请选择
北京
上海
广州
深圳
选择: @selectedCity
是否同意协议:
@(isAgreed ? "已同意" : "未同意")
汇总信息:
用户名: @userName
邮箱: @email
年龄: @age
城市: @selectedCity
同意协议: @isAgreed
- @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.1 绑定特定事件
- 默认情况下,@bind 在失去焦点时更新。可以使用 @bind:event 指定触发事件:
实时绑定示例
placeholder="输入搜索内容..." />
实时搜索: @searchText
默认绑定: @normalText
- @code {
- private string searchText = string.Empty;
- private string normalText = string.Empty;
- }
- 3.2 绑定格式化
格式化绑定示例
选择的日期: @startDate.ToString("yyyy年MM月dd日")
价格: @price.ToString("C")
- @code {
- private DateTime startDate = DateTime.Today;
- private decimal price = 0.00m;
- }
-
- 自定义组件双向绑定
- 在自定义组件中实现双向绑定:
- 子组件
@Label
value="@Value"
@οninput="HandleInput"
class="form-control @AdditionalClass"
placeholder="@Placeholder" />
@if (!string.IsNullOrEmpty(ValidationMessage))
{
@ValidationMessage
}
- @code {
-
Parameter
- public string Value { get; set; } = string.Empty;
-
Parameter
- public EventCallback 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);
- }
- }
- 父组件使用
自定义组件双向绑定
@bind-Value="userName"
Label="用户名"
Placeholder="请输入用户名" />
@bind-Value="email"
Label="邮箱"
Placeholder="请输入邮箱地址"
ValidationMessage="@(IsValidEmail ? "" : "邮箱格式不正确")" />
用户名: @userName
邮箱: @email
- @code {
- private string userName = string.Empty;
- private string email = string.Empty;
- private bool IsValidEmail => email.Contains("@") && email.Contains(".");
- }
- 5.复杂对象绑定
复杂对象绑定
用户信息
姓名:
年龄:
地址:
当前用户信息:
@userInfoJson
重置用户
创建新用户
- @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
按钮 2
按钮 3
最后点击的按钮: @lastClickedButton
点击次数: @clickCount
- @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 异步事件处理
异步事件处理
@if (isLoading)
{
加载中...
}
else
{
模拟异步操作
}
操作结果: @operationResult
耗时: @elapsedTime 毫秒
- @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.1 输入事件
表单事件处理
输入文本:
@οnchange="HandleChange"
class="form-control"
placeholder="输入内容..." />
实时输入: @inputValue | 变化事件: @changeValue
选择选项:
请选择
选项一
选项二
选项三
选择的值: @selectedValue
同意条款
状态: @(isChecked ? "已选中" : "未选中")
用户名:
邮箱:
提交表单
表单数据:
@System.Text.Json.JsonSerializer.Serialize(user, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })
提交状态: @submitStatus
- @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.1 鼠标事件
鼠标事件
@οnmοusedοwn="HandleMouseDown"
@οnmοuseup="HandleMouseUp"
@οnmοusemοve="HandleMouseMove"
@οnmοuseοver="HandleMouseOver"
@οnmοuseοut="HandleMouseOut"
@οnclick="HandleAreaClick"
@οndblclick="HandleDoubleClick"
style="width: 300px; height: 200px; border: 2px solid #007bff; padding: 20px; margin: 10px 0;">
鼠标交互区域
事件日志:
-
@foreach (var log in eventLogs.TakeLast(10).Reverse())
{
- @log
}
鼠标位置: (@mouseX, @mouseY)
按钮状态: @(isMouseDown ? "按下" : "释放")
悬停状态: @(isMouseOver ? "在区域内" : "在区域外")
- @code {
- private double mouseX = 0;
- private double mouseY = 0;
- private bool isMouseDown = false;
- private bool isMouseOver = false;
- private List eventLogs = new List();
- 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 键盘事件
键盘事件
@οnkeyup="HandleKeyUp"
@οnkeypress="HandleKeyPress"
class="form-control"
placeholder="在这里输入并观察键盘事件..." />
键盘事件日志:
-
@foreach (var log in keyEventLogs.TakeLast(10).Reverse())
{
- @log
}
最后按下的键: @lastKey
Ctrl 按下: @(isCtrlPressed ? "是" : "否")
Shift 按下: @(isShiftPressed ? "是" : "否")
Alt 按下: @(isAltPressed ? "是" : "否")
- @code {
- private string lastKey = "无";
- private bool isCtrlPressed = false;
- private bool isShiftPressed = false;
- private bool isAltPressed = false;
- private List keyEventLogs = new List();
- 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);
- }
- }
-
- 焦点和剪贴板事件
焦点和剪贴板事件
焦点测试输入框:
@οnblur="HandleBlur"
class="form-control"
placeholder="点击获取焦点,点击别处失去焦点" />
复制粘贴测试:
<p> @oncut="HandleCut"</p> <p> @οnpaste="HandlePaste"</p> <p> class="form-control"</p> <p> rows="3"</p> <p> placeholder="在这里测试复制、剪切、粘贴操作">这是一些测试文本
事件状态:
焦点状态: @(hasFocus ? "有焦点" : "无焦点")
最后操作: @lastOperation
剪贴板内容: @clipboardContent
- @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.1 事件参数封装
自定义事件处理
父级区域(点击会触发)
@onclick:stopPropagation
class="btn btn-primary">
子按钮(点击不会冒泡)
@onclick:preventDefault
class="btn btn-secondary">
阻止默认行为的按钮
自定义操作:
操作1
操作2
异步操作
操作日志:
-
@foreach (var log in actionLogs.TakeLast(5).Reverse())
{
- @log
}
-
@code {
-
private List actionLogs = new List();
-
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.1 性能优化
事件处理性能优化
@foreach (var item in items)
{
@item.Name
删除
}
@foreach (var item in largeList)
{
@item.Name
}
操作日志:
-
@foreach (var log in actionLogs.TakeLast(5).Reverse())
{
- @log
}
-
@code {
-
private List items = new List
-
{
-
new Item { Id = 1, Name = "项目1" },
-
new Item { Id = 2, Name = "项目2" },
-
new Item { Id = 3, Name = "项目3" }
-
};
-
private List largeList = Enumerable.Range(1, 100)
-
.Select(i => new Item { Id = i, Name = $"项目{i}" })
-
.ToList();
-
private List actionLogs = new List();
-
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;
-
}
-
}