• Blazor和Vue对比学习(基础1.4):事件和子传父


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

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

     

     

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

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

     

     

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

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

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

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

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

    步骤①:子组件定义事件

    复制代码
    //Vue(事件是childEvent1,emits是defineEmits返回的一个对象,通过它可以触发事件):
    const emits = defineEmits( [‘childEvent1’] )
    
    //Blazor(事件是ChildEvent,EventCallback是Blazor内置的事件类型):
    [Parameter]
    public EventCallback<string> ChildEvent1 { get; set; }
    复制代码

     

    步骤②:父组件订阅事件

    复制代码
    //Vue
    //使用v-on:指令,简写为@。
     
    
    //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的数时,将这个数传递给父组件,并在父组件上显示

    复制代码
    //Vue=====================================
    //【下面Vue的代码有个Bug,子组件第一次整除3时,触发事件传值,但之后每次递增,都会触发事件,暂时查不出哪里问题,请大佬们指定迷津】【找到原因:Add函数要写成回调格式】
    
    //子组件Child代码
    
    
    
    
    //父组件Parent代码
    
    
    
    复制代码
    复制代码
    //Blazor====================================
    //子组件Child代码
    
    class="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); } } } //父组件代码
    class = "Parent">

    灰色框里是父组件

    ParentCount:@ParentCount

    "@ParentReceived">
    @code { private int ParentCount = 0; private void ParentReceived(int msg) { ParentCount = msg; } }
    复制代码

     

     

     

    子传父参数详解

    通过以下几个方式来对比两个框架后发现,目前BlazorEventCallback,限制还是比较多,“EventCallback 旨在分配单个值,并且只能回调单个方法”,意思是只能传递一个值。下面Blazor的案例,我们使用元组Tuple来解决传递多值的问题。同时,在本章第6节,我们还将尝试结合委托,看看能不能解决Blazor传递多参数的问题,在第6节里,还将出现一个非常重要的生命周期函数StateHasChanged。

    1、传递DOM事件对象(如鼠标事件对象)

    复制代码
    //Vue===================================
    //子组件,重点在DOM触发事件时,传入DOM事件对象e,所有DOM事件绑定的回调,都会自动传入这个e对象,Blazor也一样
    
    
    
    
    
    //父组件,常规的接收参数操作
    
    
    
    
    
    
    
    //父组件按顺序接受参数
    
    
    //父组件按顺序接收参数,没有变化
    复制代码
    复制代码
    //Blazor====================================
    //子组件。因为EventCallback只能传递一个参数,所以可以考虑把DOM的事件参数,也包装到类里。和只传e一样,回调能自动传入e,而Vue不可以。
    
    
    class="Child">

    红色框里是子组件

    @code { [Parameter] public EventCallback> ChildEvent1 { get; set; } private async Task ChildEmit(MouseEventArgs e) {
    var tuple = new Tuple(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,一步完成定义事件和触发事件两个操作。但这种操作的语义不明确,而且将逻辑混在模板里,不推荐。

    复制代码
    //子组件:
    
    //定义事件doSomething,同时触发。触发事件的时候,传递两个参数
    
    
    //定义事件doSomething,同时触发。触发事件的时候,传递两个参数和鼠标事件参数