Blazor和Vue的组件事件,都使用事件订阅者模式。相对于上一章的组件属性,需要多绕一个弯,无论Blazor还是Vue,都是入门的第一个难点。要突破这个难点,一是要熟悉事件订阅模式<其实不难>;二是多练几次、熟悉套路。接下面,我们开始学习以下几个知识点

  • 事件订阅模式
  • 使用事件订阅模式实现子传父
  • 子传父参数详解
  • 事件定义的校验
  • Vue中使用模板,定义和触发事件的方法
  • Blazor中委托可以传递参数吗

一、事件订阅模式(简单的知道整个结构是怎样就可以了)

1、事件的两个主体:事件拥有者和事件订阅者
2、拥有者做的事情:定义事件,触发事件
3、订阅者做的事情:订阅事件(将自己的方法绑定到事件上),事件回调(事件被触发时执行绑定的方法)
4、事件的本质:持有 (任何类的)方法体的内存地址  的某某某,它介于变量和方法之间。说变量,是因为它只是保存了法体的内存地址(自身没有方法体),说方法是因为它可以像方法一要被触发。【注:C#里,有人将委托说成变量,是不对的】

二、使用事件订阅模式实现子传父

1、Blazor和Vue是如何应用事件订阅模式,实现子传父的?

●首先,明确事件的两个主体:

①事件拥有者:子组件,<Child></Child>
②事件订阅者:父组件,<Parent></Parent>

●其次,通过四个步骤实现子传父:子组件定义事件>父组件订阅事件>子组件触发事件>父组件响应事件

步骤①:子组件定义事件

//Vue:
const emits = defineEmits( [‘childEvent1’] ) //Blazor:
[Parameter]
public EventCallback<string> ChildEvent1 { get; set; }

步骤②:父组件订阅事件

//Vue
//使用v-on:指令,简写为@。
<Child @childEvent1 = “parentReceived”></Child> //Blazor:
//事件和属性的用法保持统一
<Child ChildEvent1 = “@ParentReceived”></Child>

步骤③:子组件触发事件

//Vue:
//emits为defineEmits方法的返回值
function childEmit(){
emits('childEvent1','I am children')
} //Blazor:
//必须异步触发事件
private async Task ChildEmit()
{
await ChildEvent1.InvokeAsync("我是子组件");
}

步骤④:父组件响应事件

//Vue:
function parentReceived(msg){
Console.log(‘收到子组件的信息为:’+msg);
} //Blazor:
Private void ParentReceived(string msg){
Console.WriteLine(‘收到子组件的信息为:’+msg)
}

2、下面举个粟子串一下:

(1)子组件上有一个数值显示框(ChildCount)和一个按钮,父组件上有一个数值显示框(ParentCount)。

(2)子组件按钮递增ChidCount,同时每逢可以整除3的数时,将这个数传递给父组件,并在父组件的ParentCount上显示

//Vue=====================================
//下面的代码有个Bug,子组件第一次整除3时,触发事件传值,但之后每次递增,都会触发事件,暂时查不出哪里问题,请大佬们指定迷津 //子组件Child代码
<template>
<div class="Child">
<h1>红色框是子组件</h1>
<h3>ChildCount:{{childCount}}</h3>
<button @click="Add">点击增加</button>
</div>
</template> <script setup>
import {ref} from 'vue'
const emits = defineEmits(['childEvent1'])
const childCount = ref(0)
function Add(){
childCount.value++
if(childCount.value % 3 === 0){
emits('childEvent1',childCount)
} }
</script> //父组件Parent代码
<template>
<div class="Parent">
<h1>灰色框是父组件</h1>
<h3>ParentCount:{{parentCount}}</h3>
<Child @childEvent1 = "parentReceived"></Child> </div>
</template> <script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const parentCount = ref(0)
function parentReceived(arg1){
parentCount.value = arg1
}
</script>
//Blazor====================================
//子组件Child代码
<div class="Child">
<h1>红色框里是子组件</h1>
<h3>ChildCount:@ChildCount</h3>
<button @onclick="Add">传值给父组件</button>
</div> @code {
private int ChildCount = 0; [Parameter]
public EventCallback<int> ChildEvent1 { get; set; } private async Task Add()
{
ChildCount++;
if (ChildCount % 3 == 0)
{
await ChildEvent1.InvokeAsync(ChildCount);
}
}
} //父组件代码
<div class = "Parent">
<h1>灰色框里是父组件</h1>
<h1>ParentCount:@ParentCount</h1>
<Child ChildEvent1="@ParentReceived"></Child>
</div> @code {
private int ParentCount = 0; private void ParentReceived(int msg)
{
ParentCount = msg;
}
}

三、子传父参数详解

通过以下几个方式来对比两个框架后发现,目前Blazor的EventCallback,限制还是很多,未来EventCallback应该像Action和Func一样,具备多重载。“EventCallback<T> 旨在分配单个值,并且只能回调单个方法”,目前只能传递单个值。本章第6节,我们尝试结合委托,看看能不能解决Blazor传递多参数的问题。

1、传递“事件触发DOM”的事件参数(如鼠标事件参数)

//Vue===================================
//子组件,重点在DOM触发事件时,传入DOM事件参数e
<template>
<div class="Child">
<h1>红色框是子组件</h1>
<button @click="childEmit">点击增加</button>
</div>
</template> <script setup>
import {ref} from 'vue'
const emits = defineEmits(['childEvent1'])
function childEmit(e){
emits('childEvent1',e)
}
</script> //父组件,常规的接收参数操作
<template>
<div class="Parent">
<h1>灰色框是父组件</h1>
<Child @childEvent1 = "parentReceived"></Child> </div>
</template> <script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
function parentReceived(e){
console.log(e)
}
//Blazor====================================
//子组件,EventCallback的泛型是一个鼠标事件类型
<div class="Child">
<h1>红色框里是子组件</h1>
<button @onclick="ChildEmit">传值给父组件</button>
</div> @code {
[Parameter]
public EventCallback<MouseEventArgs> ChildEvent1 { get; set; } private async Task ChildEmit(MouseEventArgs e)
{
await ChildEvent1.InvokeAsync(e);
}
} //父组件
<div class = "Parent">
<h1>灰色框里是父组件</h1>
<Child ChildEvent1="@ParentReceived"></Child>
</div> @code {
private void ParentReceived(MouseEventArgs e)
{
Console.WriteLine(e);
}
}

2、传递自定义参数(单个值参数、复杂参数、多个参数)

//Vue=====================================
//触发事件时,可以传递任意数量、任意类型参数
<template>
<div class="Child">
<h1>红色框是子组件</h1>
<button @click="childEmit">点击</button>
</div>
</template> <script setup>
import {ref} from 'vue'
const emits = defineEmits(['childEvent1'])
function childEmit(){
emits('childEvent1',1,'Hi',[1,2,3],{name:'MC',age:18})
}
</script> <template>
<div class="Parent">
<h1>灰色框是父组件</h1>
<Child @childEvent1 = "parentReceived"></Child> </div>
</template> //父组件按顺序接受参数
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
function parentReceived(msg1,msg2,msg3,msg4){
console.log(msg1)
console.log(msg2)
console.log(msg3[1])
console.log(msg4.name)
}
//Blazor====================================
//子组件。EventCallback只能传递一个参数,但不限制类型,如果要传递多个参数,可以使用数组或元组Tuple <div class="Child">
<h1>红色框里是子组件</h1>
<button @onclick="ChildEmit">传值给父组件</button>
</div> @code {
[Parameter]
public EventCallback<Tuple<int,string>> ChildEvent1 { get; set; } private async Task ChildEmit()
{
var tuple = new Tuple<int, string, MouseEventArgs>(1, "MC", e);
await ChildEvent1.InvokeAsync(tuple);
}
} //父组件
<div class = "Parent">
<h1>灰色框里是父组件</h1>
<Child ChildEvent1="@ParentReceived"></Child>
</div> @code {
private void ParentReceived(Tuple<int,string,MouseEventArgs> tuple)
{
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
}
}

3、同时传递DOM事件参数和自定义参数

//Vue=====================================
//子组件,触发DOM里面,以回调方式传入DOM的事件参数 <template>
<div class="Child">
<h1>红色框是子组件</h1>
<button @click="(e)=>childEmit(e)">点击</button>
</div>
</template> <script setup>
import {ref} from 'vue'
const emits = defineEmits(['childEvent1'])
function childEmit(e){
emits('childEvent1',1,'Hi',e)
}
</script> //父组件按顺序接收参数,没有变化
//Blazor====================================
//子组件。因为EventCallback只能传递一个参数,所以可以考虑也DOM的事件参数,也包装到类里 <div class="Child">
<h1>红色框里是子组件</h1>
<button @onclick="ChildEmit">传值给父组件</button>
</div> @code {
[Parameter]
public EventCallback<Tuple<int,string,MouseEventArgs>> ChildEvent1 { get; set; } private async Task ChildEmit(MouseEventArgs e)
{
var tuple = new Tuple<int, string, MouseEventArgs>(1, "MC", e);
await ChildEvent1.InvokeAsync(tuple);
}
} //父组件,略

四、事件定义的校验:

1、事件定义时,可以对事件的参数和返回值做约束。本来事件的使用,就比较绕弯烧脑,所以在还没有熟练使用事件前,可以暂且绕过这一环。

2、在Vue中,defineEmits有两种写法,一是数组写法,如defineEmits[‘事件1’, ’事件1’];二是对象写法,在对象写法中,可以定义校验。对象写法如果在JS环境下,会比较麻烦;在TS中,表达反而简明很多。同时,和props一样,JS只支持运行时校验,而TS支持编译校验。如果需要使用校验,建议直上TS。

3、Blazor是强类型,天生自带类型约束,但仅可以约束参数,无法约束返回值。以下案例,仅列举Vue的事件校验

//Vue=====================================
//JS中:比较麻烦
const emits = defineEmits({
event1:null, //不做校验
event2: (arg1,arg2) => { //校验参数
if (arg1 && arg2) {
return true
} else {
console.warn('请确定传递参数')
return false
}
}
}) //TS中:语义明确,表达简明
const emits = defineEmits<{
(e: 'event1'): void
(e: 'event1', arg1: string, arg2:string): void
}>()

五、Vue中使用模板,定义和触发事件的方法

Vue中,可以在模板中使用$emit,一步完成定义事件和触发事件两个操作。但这种操作的语义不明确,而且将逻辑混在模板里,不推荐。

//子组件:

//触发事件的时候,传递两个参数
<button @click="$emit('doSomething','arg1','arg2')"></button> //触发事件的时候,传递两个参数和鼠标事件参数
<button @click="(e)=>$emit('doSomething','arg1','arg2',e)">/button> //父组件没有变化,传来几个参数,父组件的回调函数就定义多少个参数: <Child @doSomething="receiveMsg"></Child> <script setup> //接收两个参数
function receiveMsg(msg1,msg2) {console.log(`收到子组件的信息,${msg1}-${msg2}`)} //接收两个信息和鼠标事件参数
function receiveMsg(msg1,msg2,e) {console.log(`收到子组件的信息,${msg1}-${msg2}-${e}`)}

六、Blazor中,委托可以传递参数吗?

1、子组件中,只触委托

//子组件中只触发委托,父组件渲染失败

//子组件
<div class="Child">
<h1>红色框里是子组件</h1>
<button @onclick="ChildEmit">传值给父组件</button>
</div> @code {
[Parameter]
public Action<string,int>? ChildAction1{ get; set; } private void ChildEmit()
{
ChildAction1?.Invoke("MC", 18);
}
} //父组件。代码里实际上接收到值了,但模板里没有显示。此时,新增一个按钮“<button @onclick = "@(()=>StateHasChanged())"></button>”,即可显示
<div class = "Parent">
<h1>灰色框里是父组件</h1>
<h1>@actionMsg1</h1> //但模板里无显示值
<h1>@actionMsg2</h1> //但模板里无显示值
<Child ChildAction1="@ParentReceived2"></Child>
</div> @code {
private string actionMsg1 = "";
private int actionMsg2 = 0;
//其实回调接收到参数了
private void ParentReceived2(string actionMsg1,int actionMsg2)
{
this.actionMsg1 = actionMsg1;
this.actionMsg2 = actionMsg2;
}
}

2、子组件中,事件和委托一起触发

//子组件中,同时触发委托和事件,且先触发委托,后触发事件,成功传值

//子组件
<div class="Child">
<h1>红色框里是子组件</h1>
<button @onclick="ChildEmit">传值给父组件</button>
</div> @code {
[Parameter]
public EventCallback<string> ChildEvent1 { get; set; } //事件 [Parameter]
public Action<string,int>? ChildAction1{ get; set; } //委托 private async Task ChildEmit()
{
ChildAction1?.Invoke("MC", 18); //先触发委托
await ChildEvent1.InvokeAsync("成功了"); //后触发事件
}
} //父组件
<div class = "Parent">
<h1>灰色框里是父组件</h1>
<h1>@eventMsg1</h1>
<h1>@actionMsg1</h1>
<h1>@actionMsg2</h1>
<Child ChildEvent1="@ParentReceived1" ChildAction1="@ParentReceived2"></Child>
</div> @code {
private string eventMsg1 = "";
private string actionMsg1 = "";
private int actionMsg2 = 0; private void ParentReceived1(string eventMsg1)
{
this.eventMsg1 = eventMsg1;
} private void ParentReceived2(string actionMsg1,int actionMsg2)
{
this.actionMsg1 = actionMsg1;
this.actionMsg2 = actionMsg2;
}
}

3、原因?

(1)委托和EventCallback<T>,在代码层,都可以实现组件间数据传递

(2)委托和EventCallback<T>,最主要区别,当EventCallback发生时,会调用父组件的StateHasChanged(生命周期函数),重新渲染父组件和子组件,而委托不会。所以使用委托的时候,代码层数据是传递过去了,但模板没有响应。

后记:这章有一定难度,且做了一些比较深入的尝试,建议多看几次。这章熟练了,就可以愉快的搞双向绑定了。

Blazor和Vue对比学习(基础1.4):事件和子传父的更多相关文章

  1. Blazor和Vue对比学习(基础1.5):双向绑定

    这章我们来学习,现代前端框架中最精彩的一部分,双向绑定.除了掌握原生HTML标签的双向绑定使用,我们还要在一个自定义的组件上,手撸实现双向绑定.双向绑定,是前两章知识点的一个综合运用(父传子.子传父) ...

  2. Blazor和Vue对比学习(基础1.6):祖孙传值,联级和注入

    前面章节,我们实现了父子组件之间的数据传递.大多数时候,我们以组件形式来构建页面的区块,会涉及到组件嵌套的问题,一层套一层.这种情况,很大概率需要将祖先的数据,传递给子孙后代去使用.我们当然可以使用父 ...

  3. Blazor和Vue对比学习(基础1.3):属性和父子传值

    组件除了要解决视图层展示.视图层与逻辑层的数据绑定,还需要解决一个重大问题,就是在组件树中实现数据传递,包括了父到子.子到父.祖到孙,以及任意组织之间.而我们上一章讲到的实现双向绑定的两个指令,Vue ...

  4. Blazor和Vue对比学习(基础1.7):传递UI片断,slot和RenderFragment

    组件开发模式,带来了复用.灵活.性能等优势,但也增加了组件之间数据传递的繁杂.不像传统的页面开发模式,一个ViewModel搞定整个页面数据. 组件之间的数据传递,是学习组件开发,必须要攻克的难关.这 ...

  5. Blazor和Vue对比学习:说在开始前

    1.Vue:现代前端三大框架之一(Vue/React/Angualr),基于HTML.CSS和JavaScript,2014年正式对外发布,目前已发展到3.X版本.值得说道的是,Vue的创始人作者是华 ...

  6. Blazor和Vue对比学习(进阶2.2.4):状态管理之持久化保存(2),Cookie/Session/jwt

    注:本节涉及到前后端,这个系列的对比学习,还是专注在前端Vue和Blazor技术,所以就不撸码了,下面主要学习概念. 我们知道,Http是无状态协议,客户端请求服务端,认证一次后,如果再次请求,又要重 ...

  7. Blazor和Vue对比学习(基础1.8):Blazor中实现计算属性和数据监听

    1.7章<传递UI片断>,需要做几个案例,这部分暂停消化几天.我们先把基础部分相对简单的最后两章学习了. 计算属性和数据监听是Vue当中的概念,本质上都是监听数据的变化,然后做出响应.两者 ...

  8. Blazor和Vue对比学习(基础1.9):表单输入绑定和验证,VeeValidate和EditFrom

    这是基础部分的最后一章,内容比较简单,算是为基础部分来个HappyEnding.我们分三个部分来学习: 表单输入绑定 Vue的表单验证:VeeValidate Blazor的表单验证:EditForm ...

  9. Blazor和Vue对比学习(基础1.2):模板语法和Razor语法

    Vue使用模板语法,Blazor使用祖传的Razor语法,从逻辑和方向上看,两者极为相似,比如: 都基于HTML 都通过声明式地将组件实例的状态(数据/方法)绑定到呈现的DOM上 都通过指令实现更加丰 ...

随机推荐

  1. MySQL 中有哪几种锁?

    1.表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 2.行级锁:开销大,加锁慢:会出现死锁:锁定粒度最小,发生锁冲突的概率最低,并发度也最高. 3.页面锁:开销和 ...

  2. java中的异常体系?throw和throws的区别?

    一.java中的异常体系 Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常.其中异常类Exception又分为运行时异常(Ru ...

  3. 学习zabbix(一)

    一.zabbix监控简介 server: 通过收集snmp和agent发送的数据,写入数据库(MySQL,ORACLE),再通过 php+apache 在 web 前端展示. agent:主机通过安装 ...

  4. vue2与vue3的区别

    template <template> <div class="wrap"> <div>{{ num }}</div> <Bu ...

  5. C语言之关键字(知识点2)

    关键字又叫保留字,这些关键字不可以再次定义 解析

  6. BMZCTF 端午节就该吃粽子

    端午节就该吃粽子 题目如下让我们访问login.php 然后就一个登录界面查看源码发现index.php 我们直接访问发现没有结果使用伪协议读取 然后我们使用base64解密 <?php err ...

  7. 顺利通过EMC实验(14)

  8. .NET Core Ecosystem

    .NET .NET Blog Application Models Web Mobile Desktop Microservices Gaming Machine Learning Cloud Int ...

  9. 使用Google Closure Compiler高级压缩Javascript代码

    背景 前端开发中,特别是移动端,Javascript代码压缩已经成为上线必备条件. 如今主流的Js代码压缩工具主要有: 1)Uglify http://lisperator.net/uglifyjs/ ...

  10. C++:Abstract class : invalid abstract return type for member function ‘virtual...’

    #include <iostream> #include <cmath> #include <sstream> using namespace std; class ...