Blazor和Vue对比学习(基础1.4):事件和子传父
阅读原文时间:2022年05月11日阅读:1

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

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

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

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

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

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

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

①事件拥有者:子组件,
②事件订阅者:父组件,

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

步骤①:子组件定义事件

//Vue:
const emits = defineEmits( [‘childEvent1’] )

//Blazor:
[Parameter]
public EventCallback ChildEvent1 { get; set; }

步骤②:父组件订阅事件

//Vue
//使用v-on:指令,简写为@。
@childEvent1 = “parentReceived”>

//Blazor:
//事件和属性的用法保持统一

步骤③:子组件触发事件

//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代码

//父组件Parent代码

//Blazor====================================
//子组件Child代码

红色框里是子组件

ChildCount:@ChildCount

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

}

//父组件代码

灰色框里是父组件

ParentCount:@ParentCount

@code {
private int ParentCount = 0;

private void ParentReceived(int msg)  
{  
    ParentCount = msg;  
}  

}

三、子传父参数详解

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

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

//Vue===================================
//子组件,重点在DOM触发事件时,传入DOM事件参数e

//父组件,常规的接收参数操作

//父组件按顺序接受参数

//父组件按顺序接收参数,没有变化

//Blazor====================================
//子组件。因为EventCallback只能传递一个参数,所以可以考虑也DOM的事件参数,也包装到类里

红色框里是子组件

@code {
[Parameter]
public EventCallback> 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,一步完成定义事件和触发事件两个操作。但这种操作的语义不明确,而且将逻辑混在模板里,不推荐。

//子组件:

//触发事件的时候,传递两个参数

//触发事件的时候,传递两个参数和鼠标事件参数