前言
现在的前端开发,几乎都是基于组件的研发模式,即把每一块独立的功能切割成独立的组件,然后在大组件里根据需要自由组合这些小组件,使得组件的复用性提高,而小组件更容易测试和修改,从而提升研发的效率。比如按钮、文本框、对话框、点评星星等等都是可以被任意复用的小组件。
那么问题来了,组件之间是怎么进行通信呢?
父子组件之间的通信
当前组件即父组件,嵌套了一个封装好的组件,因此称这个组件为该组件的子组件。比如对话框。
父组件操作子组件
<button class="btn btn-primary" @onclick="e=>modal.Show()">对话框</button>
<BootModal @ref="modal">
<HeadTemplate>
<h4>你好</h4>
</HeadTemplate>
<BodyTemplate>
弹出一个对话框
</BodyTemplate>
</BootModal>
@code{
BootModal modal;
}
我们使用了一个对话框的组件
BootModal
,然后通过一个@ref
关键字,可以让这个组件与该组件的实例进行关联,这样我们就可以通过代码操作这个实例了,比如调用方法。
回到我定义的一个按钮上,触发一个 @onclick
事件,然后调用这个 modal
的 Show
方法弹出对话框。
子组件变化通知父组件
通过
EventCallback
事件的触发,使订阅该事件的对象得到触发后的通知。
在子组件中,可以定义一个 EventCallback
类型的属性,称为事件。
注意:这里的事件不是 C# 的
event
类型,而是一个EventCallback
委托类型。
然后在需要的时候触发这个事件,被订阅的对象即可被执行,类似于订阅者模式。
[Parameter] public EventCallback On[EventName] { get; set;}
按照规范,事件命名应符合 On + 动词 的形式,例如 OnDelete/ OnCreate/OnClick 等。
子组件
封装了一个简单的按钮,并暴露了一个
OnClick
事件。
<button class="btn btn-warning btn-lg" @onclick="Click">
@Text
</button>
@code{
[Parameter] public string Text { get; set; }
[Parameter] public EventCallback OnClick { get; set; }
public Task Click()
{
return OnClick.InvokeAsync(null);
}
}
父组件
在父组件里订阅了这个事件。
<BigButton Text="提交" OnClick="@(e=>JS.InvokeVoidAsync("alert","我是一个警告框"))"/>
@inject IJSRuntime JS
最终效果
子组件与子组件的通信
示例场景:在一个页面中,左侧是一个列表(组件 List),右侧是详情(组件 Detail),当点击列表的数据时,右侧显示相应数据的详情。
从场景中可以看出,一个页面有 List 和 Detail 两个子组件,而现在是需要当 List 组件的某一行数据被选中,则 Detail 组件要显示这条数据的具体内容。
大致效果是这样的:
Index 代码(即父组件代码)
<div class="row">
<div class="col-6">
<List OnSelectItem="Select"/>
</div>
<div class="col">
<Detail SelectedItem="SelectItem"/>
</div>
</div>
@code{
WeatherForecast SelectItem { get; set; }
void Select(WeatherForecast item)
{
SelectItem = item;
}
}
List 组件代码
<table class="table">
<thead>
<tr>
<th>日期</th>
<th>摄氏度</th>
<th>华氏度</th>
<th>说明</th>
</tr>
</thead>
<tbody>
@if (Data == null)
{
<tr>
<td colspan="4">数据加载中...</td>
</tr>
}
else
{
foreach (var item in Data)
{
<tr @onclick="e => SelectItem(item)">
<td>@item.Date</td>
<td>@item.TemperatureC</td>
<td>@item.TemperatureF</td>
<td>@item.Summary</td>
</tr>
}
}
</tbody>
</table>
@inject WeatherForecastService WeatherForecastService
@code{
IEnumerable<WeatherForecast> Data { get; set; }
protected override async Task OnInitializedAsync()
{
Data = await WeatherForecastService.GetForecastAsync(DateTime.Now);
}
[Parameter] public EventCallback<WeatherForecast> OnSelectItem { get; set; }
async Task SelectItem(WeatherForecast item)
{
await OnSelectItem.InvokeAsync(item);
}
}
Detail 组件代码
<h3>详情</h3>
<hr />
<div class="form-group">
日期:@SelectedItem?.Date
</div>
<div class="form-group">
摄氏度:@SelectedItem?.TemperatureC
</div>
<div class="form-group">
华氏度:@SelectedItem?.TemperatureF
</div>
<div class="form-group">
说明:@SelectedItem?.Summary
</div>
@code {
[Parameter]public WeatherForecast SelectedItem { get; set; }
}
原理介绍
-
在 List 组件
定义一个OnSelectItem
事件,当点击行时,即@onclick
事件以后,触发OnSelectItem
事件通知订阅方,并传入一个当前点击的数据项。 -
回到页面当中,
OnSelectItem
事件订阅了一个方法Select
,把选中的项赋值给一个等候很久的SelectItem
变量。 -
把该变量给
Detail
事先声明好的一个参数赋值。
顺序大致如下
这样就完成了两个子组件之间的通信。
总结
通过子组件暴露的 EventCallback
事件,可以让父组件通过订阅事件来与子组件进行通信交互,这样就可以实现组件之间的互联互通操作,从而使组件之间更具有联动性。
插播广告
BootBlazorUI 基于 Bootstrap 样式为 Blazor 量身打造的前端组件库,不依赖 JQ 和 bootstrap.js,并支持 Mvc / Razor 页面和现在最新的 Blazor 。该组件立志于交互性和实用性,而不仅仅是一个静态UI库。