Blazor入门笔记(6)-组件间通信

时间:2023-02-16 21:17:07

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>

效果如下:

Blazor入门笔记(6)-组件间通信

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;
}
}

效果如下:

Blazor入门笔记(6)-组件间通信

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值。效果如下:

Blazor入门笔记(6)-组件间通信

注意:

(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事件。效果如下:

Blazor入门笔记(6)-组件间通信

5.非嵌套组件间通信

非嵌套组件也就是说在渲染树中,任一组件无法向上或向下寻找到另外一个组件,例如兄弟组件、叔父组件等。非嵌套组件之间通信,可以通过共同的祖/父组件进行通信,但是这样设计模式并不友好,因此我们可以利用一个静态类使用事件订阅模式进行来进行通信。

静态类EventDispatcher的定义如下:

public static class EventDispatcher
{
private static Dictionary<string, Action<object>> _actions;
static EventDispatcher()
{
_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接口。现在效果如下:

Blazor入门笔记(6)-组件间通信

代码:BlazorCrossComponentInterop

本文参考:

创建和使用 ASP.NET Core Razor 组件

ASP.NET Core Blazor 事件处理

Blazor入门笔记(6)-组件间通信的更多相关文章

  1. python 全栈开发,Day91&lpar;Vue实例的生命周期&comma;组件间通信之*事件总线bus&comma;Vue Router&comma;vue-cli 工具&rpar;

    昨日内容回顾 0. 组件注意事项!!! data属性必须是一个函数! 1. 注册全局组件 Vue.component('组件名',{ template: `` }) var app = new Vue ...

  2. vue组件间通信

    组件间通信(父子,兄弟) 相关链接\组件通信http://www.cnblogs.com/xulei1992/p/6121974.html 学习链接Vue.js--60分钟快速入门http://www ...

  3. React独立组件间通信联动

    React是现在主流的高效的前端框架,其官方文档 http://reactjs.cn/react/docs/getting-started.html 在介绍组件间通信时只给出了父子组件间通信的方法,而 ...

  4. 聊聊Vue&period;js组件间通信的几种姿势

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/a ...

  5. 【Vue】利用父子组件间通信实现一个场景

    组件间通信是组件开发的,我们既希望组件的独立性,数据能互不干扰,又不可避免组件间会有联系和交互. 在vue中,父子组件的关系可以总结为props down,events up: 在vue2.0中废弃了 ...

  6. React 精要面试题讲解&lpar;二&rpar; 组件间通信详解

    单向数据流与组件间通信 上文我们已经讲述过,react 单向数据流的原理和简单模拟实现.结合上文中的代码,我们来进行这节面试题的讲解: react中的组件间通信. 那么,首先我们把看上文中的原生js代 ...

  7. vue&lowbar;组件间通信:自定义事件、消息发布与订阅、槽

    自定义事件 只能用于 子组件 向 父组件 发送数据 可以取代函数类型的 props 在父组件: 给子组件@add-todo-event="addTodo" 在子组件: 相关方法中, ...

  8. Vue的父子组件间通信及借助&dollar;emit和&dollar;on解除父子级通信的耦合度高的问题

    1.父子级间通信,父类找子类非常容易,直接在子组件上加一个ref,父组件直接通过this.$refs操作子组件的数据和方法    父 这边子组件中 就完成了父 => 子组件通信 2. 子 =&g ...

  9. React 组件间通信介绍

    React 组件间通信方式简介 React 组件间通信主要分为以下四种情况: 父组件向子组件通信 子组件向父组件通信 跨级组件之间通信 非嵌套组件间通信 下面对这四种情况分别进行介绍:   父组件向子 ...

随机推荐

  1. const和readonly的区别

    http://www.cnblogs.com/royenhome/archive/2010/05/22/1741592.html http://www.codeproject.com/Tips/803 ...

  2. 防御SQL注入的方法总结

    这篇文章主要讲解了防御SQL注入的方法,介绍了什么是注入,注入的原因是什么,以及如何防御,需要的朋友可以参考下   SQL 注入是一类危害极大的攻击形式.虽然危害很大,但是防御却远远没有XSS那么困难 ...

  3. 智能卡安全机制比较系列(六) TimeCOS

    TimeCOS是握奇公司推出的智能卡操作系统,也可以说是国内早期自己开发的为数不多的几款COS之一.当然随着后来国内公司对于CPU卡开发的投入,其他公司的COS产品也纷纷推出. 其实从握奇的TimeC ...

  4. Html中行内元素有哪些?块级元素有哪些?

    1.关于行内元素和块状元素的说明 根据CSS规范的规定,每一个网页元素都有一个display属性,用于确定该元素的类型,每一个元素都有默认的display属性值,比如div元素,它的默认display ...

  5. vue开发(开发环境&plus;项目搭建)

    Vue.js是一套构建用户界面的渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合.另一方面,Vu ...

  6. Linux下1号进程的前世&lpar;kernel&lowbar;init&rpar;今生&lpar;init进程&rpar;----Linux进程的管理与调度(六)

    前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel ...

  7. shell 关于路径查询显示pwd

    获取根目录:dirname $0 cd到根目录:cd `dirname $0` 获取当前目录:pwd 因此,要获取当前目录应该是: cd `dirname $` && pwd 或者 $ ...

  8. openstack--8--控制节点部署Dashboard

    Horizon介绍 Dashboard服务,这里具体的产品就是Horizon1.它提供一个Web界面操作Openstack的系统2.使用Django框架基于Openstack API开发3.支持将Se ...

  9. (原创)一个和c&num;中Lazy&lt&semi;T&gt&semi;类似的c&plus;&plus; Lazy&lt&semi;T&gt&semi;类的实现

    在.net 4.0中增加一个延迟加载类Lazy<T>,它的作用是实现按需延迟加载,也许很多人用过.一个典型的应用场景是这样的:当初始化某个对象时,该对象引用了一个大对象,需要创建,这个对象 ...

  10. python input输入元素相加

    sum= number= while True: : break number=int(input('数字0为结束程序,请输入数字: ')) sum+=number print('目前累加的结果为: ...