zoukankan      html  css  js  c++  java
  • Blazor入门笔记(6)-组件间通信

    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>

    效果如下:

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

    效果如下:

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

    注意:

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

    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接口。现在效果如下:

    代码:BlazorCrossComponentInterop

    本文参考:

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

    ASP.NET Core Blazor 事件处理

  • 相关阅读:
    流程图的标准画法
    java应用,直接请求没问题,通过nginx跳转状态吗400
    jenkins启动java项目的jar包总是退出
    可以通过下面的脚本直观的看一下当前系统中所有进程的得分情况:
    sonarqube安装的坑
    Windows共享设定-使用net use添加网络盘带上账号密码
    Synctoy2.1使用定时任务0X1
    如果报错,使用 journalctl -f -t etcd 和 journalctl -u etcd 来定位问题。
    NodePort 只能在node节点上访问,外部无法访问
    mysql5.7 yum安装
  • 原文地址:https://www.cnblogs.com/zxyao/p/12671873.html
Copyright © 2011-2022 走看看