1. 环境
- VS2019 16.5.1
- .NET Core SDK 3.1.200
- Blazor webAssembly Templates 3.2.0-preview2.20160.5
2. 简介
在使用 Blazor 时, 避免不了要进行组件间通信, 组件间的通信大致上有以下几种:
(1) 父, 子组件间通信;
(2) 多级组件组件通信, 例如祖, 孙节点间通信;
(3) 非嵌套组件间通信.
Blazor 支持数据的双向绑定, 这里主要介绍单向绑定的实现.
3. 父, 子组件间通信
父, 子组件间通信分为两类: 父与子通信, 子与父通信.
3.1. 父与子通信
父与子通信是最为简单的, 直接通过数据绑定即可实现:
- Self1.razor
- <div class="bg-white p-3" style="color: #000;">
- <h3>Self1</h3>
- <p>parent: @Value</p>
- </div>
- @code {
- [Parameter]
- public string Value { get; set; }
- }
- Parent1.razor
- <div class="bg-primary jumbotron text-white">
- <h3>Parent1</h3>
- <Self1 Value="I'm from Parent1"></Self1>
- </div>
效果如下:
3.2. 子与父通信
子与父通信是通过回调事件实现的, 通过将事件函数传递到子组件, 子组件在数据发生变化时, 调用该回调函数即可. 在 Self1.razor 和 Parent1.razor 组件上进行修改, 为 Self1 组件基础上添加一个事件 OnValueChanged, 并在数据 Value 发生变化时执行该事件, 通知父组件新数据是什么, 在这里, 我没有在子组件中更新 Value 的值, 因为新的数据会从父组件流到子组件中. 现在的得到的组件 Self2.razor 和 Parent2.razor 的代码如下:
- Self2.razor
- <div class="bg-white p-3" style="color: #000;">
- <h3>Self1</h3>
- <button @onclick="ChangeValue">ChangeValue</button>
- <p>parent: @Value</p>
- </div>
- @code {
- [Parameter]
- public string Value { get; set; }
- [Parameter]
- public EventCallback<string> OnValueChanged { get; set; }
- private async Task ChangeValue()
- {
- string newValue = DateTime.Now.ToString("o");
- if (OnValueChanged.HasDelegate)
- {
- await OnValueChanged.InvokeAsync(newValue);
- }
- }
- }
- Parent2.razor
- <div class="bg-primary jumbotron text-white">
- <h3>Parent2</h3>
- <p>@_value</p>
- <Self2 Value="@_value" OnValueChanged="@OnValueChanged"></Self2>
- </div>
- @code{
- private string _value = "I'm from Parent2";
- private void OnValueChanged(string val)
- {
- _value = val;
- }
- }
效果如下:
3.3. 使用 @bind
@bind 支持数据的双向绑定, 但是当子组件发生变化时, 依然需要调用回调事件, 不过好处就是回调事件不用你写, 这个在 blazor 入门笔记 (5)- 数据绑定中有实现.
4. 祖, 孙组件间通信
祖, 孙组件间的通信也分为两类: 祖与孙通信, 孙与祖通信. 最暴力的方法就是通过父节点中转, 实现祖 - 父 - 孙通信, 但是当跨越多个层级的时候就比较麻烦, 好在 Blazor 提供了 "Cascading values and parameters", 中文翻译为级联值和参数. 级联值和参数是通过 CascadingValue 组件和 CascadingParameter 属性注解实现的.
4.1. 祖与孙通信
先上代码:
- Self3.razor
- <div class="bg-white p-3" style="color: #000;">
- <h3>Self3</h3>
- <p>GrandValue: @GrandValue</p>
- </div>
- @code {
- /// <summary>
- /// Name 参数必须与 Name 带有 CascadingValue 组件的属性匹配, 如果我们没有注明 Name, 则会通过类型匹配一个最相似的属性
- /// </summary>
- [CascadingParameter(Name = "GrandValue")]
- string GrandValue { get; set; }
- }
- Parent3.razor
- <div class="bg-primary jumbotron text-white">
- <h3>Parent3</h3>
- <Self3></Self3>
- </div>
- Grand3.Razor
- <h3>Grand3</h3>
- <p>GrandValue:@_grandValue</p>
- <CascadingValue Value="@_grandValue" Name="GrandValue">
- <Parent3 />
- </CascadingValue>
- @code {
- private string _grandValue = "GrandValue";
- }
我们在 Grand3 组件中使用 CascadingValue 组件包裹了 Parent3 组件, 并为组件添加了一个 Value 参数和一个 Name 参数, 并将_grandValue 赋给了 Value.Parent3 组件中没有做任何事情, 仅使用 Self3 组件. 在 Self3 中声明了一个 GrandValue 的属性, 并在这个属性上使用了 CascadingParameter 属性注解, CascadingParameter 指定了 Name 为我们再 Grand3 组件中 CascadingValue 组件的 Name 参数的值. 这样, 我们就可以在 Self3 组件中获取到 Grand3 组件中的_grandValue 值. 效果如下:
注意:
(1) CascadingParameter 所声明的属性可以是 private
(2) CascadingValue 和 CascadingParameter 可以不指定 Name, 这时将会通过类型进行匹配.
当我们如果有多个参数需要从祖传递到孙怎么办呢? 有两种方法:
(1) 嵌套使用 CascadingValue
CascadingValue 组件运行嵌套使用, 可以在祖组件中嵌套 CascadingValue, 而孙组件中则只需要将所有的来自祖组件的参数使用 CascadingParameter 进行声明即可. 需要注意的是, 如果指定 Name, 请确保每个 Name 都是唯一的.
(2) 使用 Model 类
CascadingValue 可以是 class, 因此可以将所有的需要传递的参数使用一个 class 进行封装, 然后传递到孙组件, 孙组件使用同类型的 class 接收该参数即可.
4.2. 孙与祖通信
孙与祖通信与子与父通信一样, 需要使用事件进行回调, 这个回调方法也是一个参数, 因此只需要将该回调也通过 CascadingValue 传递到孙组件中, 当孙组件数据发生变化时调用该回调函数即可. 传递的方法如 4.1. 所示有两种, 但是无论哪种都需要在祖组件中手动调用 StateHasChanged. 另外, 如果直接更新值或者引用, 请不要在孙组件中直接更新, 只需要调用回掉即可, 因为会触发两次渲染 (可以在代码的 GrandX 看到). 当然, 如果是引用中的值, 比如 model 中的值, 是需要在子组件中更新的. 这里我们将参数和回调封装成一个类:
- public class CascadingModel<T>
- {
- public CascadingModel()
- {
- }
- public CascadingModel(T defaultValue)
- {
- _value = defaultValue;
- }
- public Action StateHasChanged;
- private T _value;
- public T Value
- {
- get => _value;
- set
- {
- _value = value;
- StateHasChanged?.Invoke();
- }
- }
- }
组件中代码如下:
- Self4.razor
- <div class="bg-white p-3" style="color: #000;">
- <h3>Self4</h3>
- <p>GrandValueModel-GrandValue: @CascadingModel.Value</p>
- <button @onclick="ChangGrandValue">Chang GrandValue</button>
- </div>
- @code {
- [CascadingParameter(Name = "GrandValue")]
- CascadingModel<string> CascadingModel { get; set; }
- void ChangGrandValue()
- {
- CascadingModel.Value = "I'm Form self:" + DateTime.Now.ToString("HH:mm:ss");
- }
- }
- Parent4.razor
- <div class="bg-primary jumbotron text-white">
- <h3>Parent4</h3>
- <Self4></Self4>
- </div>
- Grand4.razor
- <h3>Grand4</h3>
- <p>GrandValue:@_cascadingModel.Value</p>
- <CascadingValue Value="@_cascadingModel" Name="GrandValue">
- <Parent4 />
- </CascadingValue>
- @code {
- private CascadingModel<string> _cascadingModel = new CascadingModel<string>("GrandValue");
- protected override void OnInitialized()
- {
- _cascadingModel.StateHasChanged += StateHasChanged;
- base.OnInitialized();
- }
- private void ChangeGrandValue()
- {
- _cascadingModel.Value = DateTime.Now.ToString("o");
- }
- }
在 Grand4 组件中, 我们需要在组件初始化的时候为 CascadingModel 绑定 StateHasChanged 事件. 效果如下:
5. 非嵌套组件间通信
非嵌套组件也就是说在渲染树中, 任一组件无法向上或向下寻找到另外一个组件, 例如兄弟组件, 叔父组件等. 非嵌套组件之间通信, 可以通过共同的祖 / 父组件进行通信, 但是这样设计模式并不友好, 因此我们可以利用一个静态类使用事件订阅模式进行来进行通信.
静态类 EventDispatcher 的定义如下:
- public static class EventDispatcher
- {
- private static Dictionary<string, Action<object>> _actions;
- static EventDispatch()
- {
- _actions = new Dictionary<string, Action<object>>();
- }
- public static void AddAction(string key, Action<object> action)
- {
- if (!_actions.ContainsKey(key))
- {
- _actions.Add(key, action);
- }
- else
- {
- throw new Exception($"event key{key} has existed");
- }
- }
- public static void RemoveAction(string key)
- {
- if (_actions.ContainsKey(key))
- {
- _actions.Remove(key);
- }
- }
- public static void Dispatch(string key, object value)
- {
- Console.WriteLine("Dispatch");
- Console.WriteLine(string.Join(",", _actions.Keys));
- if (_actions.ContainsKey(key))
- {
- var act = _actions[key];
- act.Invoke(value);
- }
- }
- }
EventDispatcher 内部使用一个字典来保存所有的事件, 通过 AddAction 实现事件的注册, RemoveAction 实现事件的移出, Dispatch 实现事件的发送. 每当初始化一个组件时 (OnInitialized), 我们使用 AddAction 注册一个用于更新本组件内部状态的事件; 在卸载组件时 (Dispose), 使用 RemoveAction 将该事件从 EventDispatcher 中删除; 当其他组件需要更新本组件的内部状态时, 触发 Dispatch 即可.
接下来展示一个叔侄之间通信的实例:
- Self5.razor
- @implements IDisposable
- <div class="bg-white p-3" style="color: #000;">
- <h3>Self5</h3>
- <span>UpdateNephewValue</span>
- <input class="w-75" @bind="UncleValue" />
- <p>Update From Uncle: @_nephewValue</p>
- </div>
- @code {
- private string _uncleValue = "I'm default uncleValue from nephew";
- private string _nephewValue;
- string UncleValue
- {
- get => _uncleValue;
- set
- {
- _uncleValue = value;
- EventDispatcher.Dispatch("UpdateUncle", _uncleValue);
- }
- }
- protected override void OnInitialized()
- {
- EventDispatcher.AddAction("UpdateNephew", (value) =>
- {
- _nephewValue = (string)value;
- StateHasChanged();
- });
- base.OnInitialized();
- }
- protected override void OnAfterRender(bool firstRender)
- {
- if (firstRender)
- {
- EventDispatcher.Dispatch("UpdateUncle", _uncleValue);
- }
- base.OnAfterRender(firstRender);
- }
- public void Dispose()
- {
- EventDispatcher.RemoveAction("UpdateNephew");
- }
- }
- Uncle5.razor
- @implements IDisposable
- <div class="bg-primary jumbotron m-1 text-white">
- <h3>Uncle5</h3>
- <span>UpdateNephewValue</span>
- <input class="w-75" @bind="NephewValue" />
- <p>Update From Nephew: @_uncleValue</p>
- </div>
- @code {
- private string _uncleValue;
- private string _nephewValue = "I'm default nephew from uncle";
- string NephewValue
- {
- get => _nephewValue;
- set
- {
- _nephewValue = value;
- Console.WriteLine("_nephewValue has changed");
- EventDispatcher.Dispatch("UpdateNephew", _nephewValue);
- }
- }
- protected override void OnInitialized()
- {
- EventDispatcher.AddAction("UpdateUncle", (value) =>
- {
- _uncleValue = (string)value;
- StateHasChanged();
- });
- base.OnInitialized();
- }
- protected override void OnAfterRender(bool firstRender)
- {
- if (firstRender)
- {
- EventDispatcher.Dispatch("UpdateNephew", _nephewValue);
- }
- base.OnAfterRender(firstRender);
- }
- public void Dispose()
- {
- EventDispatcher.RemoveAction("UpdateUncle");
- }
- }
- Parent5.razor
- <div class="bg-primary jumbotron m-1 text-white">
- <h3>Parent5</h3>
- <Self5></Self5>
- </div>
- Grand5.Razor
- <h3>Grand5</h3>
- <Parent5 />
- <Uncle5/>
在实例中, 我们再 Self5 和 Uncle5 中都设置了默认值, 但是由于两个组件无法得知另外一个组件 OnInitialized 是否已经执行, 因此在 OnAfterRender 中在第一次渲染结束后触发相应的事件, 以实现默认值的传递. 为了在组件销毁时能给移出事件, Self5 和 Uncle5 都还继承了 IDisposable 接口. 现在效果如下:
代码: BlazorCrossComponentInterop
本文参考:
创建和使用 ASP.NET Core Razor 组件
ASP.NET Core Blazor 事件处理
来源: https://www.cnblogs.com/zxyao/p/12671873.html