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 事件處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM