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. 修改openstack Centos镜像的默认用户的密码

    Ubuntu官方提供的OpenStack镜像是用Key来登录的,太麻烦,可以改成用密码来登录. 修改image的工具叫:guestfish. yum install libguestfs-tools ...

  2. 学习 Haproxy (二)

    1. Haproxy介绍 Haproxy是一个开源的高性能的反向代理或者说是负载均衡服务软件之一,它支持双机热备.虚拟主机.基于TCP和HTTP应用代理等功能.其配置简单,而且拥有很好的对服务器节点的 ...

  3. maven项目改造成springboot项目

    springboot项目其实归根到底就是一个maven项目,通常我们创建springboot项目,只要使用idea中的spring Initializr就可以创建就可以了. 今天我们来讲下如何改造一个 ...

  4. SVN在拉取(更新)代码的时候出现Error:svn: E155037: Previous operation has not finished; run 'cleanup' if it was interrupted问题 ---window版

    简易方法1 今天朋友看到朋友报错这个错误,偷偷学习了下他的方法并做记录以防忘记 简易方法2 今天使用svn时报了一个这个错,网上搜索时都说是要使用sqllite来删除svn队列. 其实可以直接使用id ...

  5. 攻防世界 easytornado

    easytornado 进入环境就这样子 我们逐一访问看看 进入flag.txt提示flag in /fllllllllllllag我们访问fllllllllllllag看看 报了一个error,且在 ...

  6. 决策树3:基尼指数--Gini index(CART)

    既能做分类,又能做回归.分类:基尼值作为节点分类依据.回归:最小方差作为节点的依据. 节点越不纯,基尼值越大,熵值越大 pi表示在信息熵部分中有介绍,如下图中介绍 方差越小越好. 选择最小的那个0.3 ...

  7. WordPress 网站开发“微信小程序“实战(二)

    原文链接:https://devework.com/wordpres...,转载请用明链注明来源,谢谢! 本文是"WordPress 开发微信小程序"系列的第二篇,本文记录的是开发 ...

  8. 手把手教你学vue-4(vuex)

    1.首先明白vuex是做什么用的. 管理统一组件状态state.每个应用将仅仅包含一个 store 实例.单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态 ...

  9. 微信小程序命名规则

    目录分析 src是主要的开发目录,各个文件实现功能如下所示: ├─.idea │ └─libraries ├─.temp ├─config └─src ├─assets │ └─images ├─co ...

  10. python---复杂度、斐波那切数列、汉诺塔

    时间复杂度 ​ 用来估计算法运行时间的一个式子. ​ 一般来说, 时间复杂度高的算法比复杂度低的算法慢. 常见的时间复杂度: ​ O(1) < O(logn) < O(n) < O( ...