Blazor 组件通信

Blazor组件通信

文档结构

组件概述

前提知识:

  1. 组件概述
  2. ASP.NET Core Razor 组件

关于组件的说明如下:

组件 是内置到 .NET程序集.NET C#类组件类 通常以Razor标记页的形式编写。

② Blazor中的 组件 正式称为Razor组件,非正式地称为Blazor组件

Razor 是一种语法,用于将HTML标记与专为提高开发人员工作效率而设计的C#代码结合在一起。

组件 在内存中以浏览器文档对象模型(DOM)的形式存在,这种形式被称为呈现树,用于以灵活高效的方式更新UI。

组件通信

前提知识:

  1. 组件参数
  2. 数据绑定
  3. 级联值和参数
  4. 事件处理

概述

从信息传递的方向来看,Blazor组件通信方式有以下四种:

  • 组件参数:父组件向子组件传递信息
  • 级联值和参数:祖先组件向任意后代组件传递信息
  • 事件回调:子组件向父组件传递信息
  • 状态容器:任意组件间传递信息

组件参数

组件参数使用组件类中的公共CSharp属性 [Parameter]特性 进行定义,接受从父组件传递过来的数据。

image

代码说明: ChildComponent通过组件参数从ParentComponent接收TitleBody的值。

代码示例:(提示:以下代码改编于微软官方文档 ASP.NET Core Razor 组件 - 组件参数

PanelBody.cs

public class PanelBody
{
    public string? Text { get; set; }
    public string? Style { get; set; }
}

ParentComponent.razor

@page "/parameter-parent"

<h1>This is a parent component.</h1>

<ParameterChild Title="Set by Parent"
                Body="@(new PanelBody(){Text = "Set by Parent.", Style = "italic"})" />

ChildComponent.razor

<h1>This is a child component.</h1>

<div>
    <div>@Title</div>

    <div style="font-style:@Body.Style">
        @Body.Text
    </div>
</div>

@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public PanelBody Body { get; set; }
}

级联值和参数

级联值和参数提供了一种可将数据沿组件层次结构从祖先组件向下流向任意数量的后代组件的方法。

级联值和参数通过Blazor内置组件CascadingValue[CascadingParameter]特性来实现:

  • CascadingValue组件:用于在组件树中向下传递数据
  • [CascadingParameter]特性用于在子组件中接收通过CascadingValue组件传递的数据

image

常用方式

代码说明:TitleBody的参数值沿组件层次结构从ParentComponent向下流动到ChildComponent

代码示例:(提示:以下代码改编于微软官方文档 - ASP.NET Core Blazor 级联值和参数

PanelBody.cs

public class PanelBody
{
    public string? Text { get; set; }
    public string? Style { get; set; }
}

ParentComponent.razor

@page "/parameter-parent"

<h1>This is a parent component.</h1>

<CascadingValue Value="Set by Parent" Name="ChildTitle">
    <CascadingValue Value="@Body" Name="ChildBody">
        <ParameterChild />
    </CascadingValue>
</CascadingValue>

@code {
    public PanelBody Body{ get; set; } = new PanelBody(){
        Text = "Set by Parent",
        Style = "italic"
    }
}

ChildComponent.razor

<h1>This is a child component.</h1>

<div>
    <div>@Title</div>

    <div style="font-style:@Body.Style">
        @Body.Text
    </div>
</div>

@code {
    [CascadingParameter(Name = "ChildTitle")]
    public string Title { get; set; }

    [CascadingParameter(Name = "ChildBody")]
    public PanelBody Body { get; set; }
}

跨组件层次结构传递数据

前提知识:

  1. RenderFragment委托
  2. 子内容呈现片段

代码说明:父组件设置子组件的内容

代码示例:(提示:以下代码改编于微软官方文档 - ASP.NET Core Razor 组件 - 子内容呈现片段

ParentComponent.cs

<ChildComponent>
    <p>在父组件设置子组件的内容<p>
</ChildComponent>

ChildComponent.cs

<div>
    @ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

代码说明:在IndexPage页中展示Tab页集合

代码示例:(提示:以下代码改编于微软官方文档 - ASP.NET Core Blazor 级联值和参数

IndexPage.razor

在IndexPage页中显示三个Tab页。其中,TabSet用于组织和呈现Tab组件,Tab组件用于显示Tab页内容。

@page "/index_page"

<h3>级联参数</h3>

<TabSet>
    <Tab Title="First tab">
        <p>The first tab!</p>
    </Tab>

    <Tab Title="Second tab">
        <p>The second tab!</p>
    </Tab>

    <Tab Title="Third tab">
        <p>The third tab!</p>
    </Tab>
</TabSet>

TabSet.razor

其中,TabSet组件通过 <CascadingValue Value="this"> 将自身的实例作为参数传递给子组件。

注:为什么组件也可以作为参数传递呢?因为组件本质上是以的形式存在。

<CascadingValue Value="this">
    <ul>
        @ChildContent
    </ul>
</CascadingValue>

<div>
    @ActiveTab?.ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent{ get; set; }

    public Tab? ActiveTab{ get; private set; }

    public void AddTab(Tab tab)
    {
        if (ActiveTab is null)
        {
            SetActiveTab(tab);
        }
    }

    public void SetActiveTab(Tab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

Tab.razor

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet? ContainerTabSet{ get; set; }

    [Parameter]
    public string? Title{ get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public string? TitleCssClass => ContainerTabSet?.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet?.AddTab(this);
    }

    public void ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

事件回调

事件回调(EventCallback)是一种用于处理事件的标准机制,它允许子组件触发事件并将数据传递回父组件。

image

前提知识:

  1. 委托
  2. 事件

代码说明:子组件定义EventCallback类型的组件参数,接收从父组件传递的委托。

代码示例:(提示:以下代码改编于微软官方文档 - ASP.NET Core Blazor 事件处理 - EventCallback

ParentComponent.razor

@page "/parent_page"

<ChildComponent OnClickCallback="ShowMessage" />

<p>@_msg</p>

@code{
    private string? _msg;

    private void ShowMessage(MouseEventArgs e)
    {
        _msg = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})"
    }
}

ChildComponent.razor

<div>
    <button @onclick = "OnClick">
        Trigger a Parent component method
    </button>
</div>

@code{
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }

    public async Task OnClick(MouseEventArgs e)
    {
        await OnClickCallback.InvokeAsync(e);
    }
}

以下为使用BuildRenderTree重写的例子:

ChildComponent.razor

public class ChildComponent : ComponentBase
{
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }

    public async Task OnClick(MouseEventArgs e)
    {
        await OnClickCallback.InvokeAsync(e);
    }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        base.BuildRenderTree(builder);

        int sequence = 0;

        builder.OpenElement(sequence ++, "div");
        builder.OpenElement(sequence ++, "button");
        builder.AddAttribute(sequence ++, "onclick", OnClick);
        builder.AddContent(sequence, "Trigger a Parent component method");
        builder.CloseElement();
        builder.CloseElement();
    }
}

ParentComponent.razor

public class ParentComponent : ComponentBase
{
    private string? _msg;

    private void ShowMessage(MouseEventArgs e)
    {
        _msg = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
        StateHasChanged();
    }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        base.BuildRenderTree(builder);

        int sequence = 0;

        builder.OpenElement(sequence ++, "p");
        builder.AddContent(sequence ++, _msg);
        builder.CloseElement();

        builder.OpenComponent<ChildComponent>(sequence ++);
        builder.AddAttribute(sequence, "OnClickCallback", EventCallback.Factory.Create<MouseEventArgs>(this, ShowMessage));
        builder.CloseComponent();
    }
}

状态容器

嵌套组件和非嵌套组件可使用通过依赖注入容器注册并存储在内容中的状态容器来共享对数据的访问。

image

IStateContainer.cs

public interface IStateContainer
{
     event Action<string>? OnChange;
     void NotifyStateChanged(string msg);
}

StateContainer.cs

public class StateContainer
{
    public event Action<string>? OnChange;

    public void NotifyStateChanged(string msg) => OnChange?.Invoke(msg);
}

Program.cs 注册服务

builder.Services.AddSingleton<IStateContainer, StateContainer>();

DisplayPage.razor

@page "/DisplayPage"

<Component_A />

<Component_B />

<Component_C />

Component_A.razor

@inject IStateContainer SC

<button @onclick="TriggerFunc"> Component_A </button>

@code{
    private void TriggerFunc() => SC.NotifyStateChanged("Component_A触发事件");
}

Component_B.razor

@inject IStateContainer SC
@implements IDisposable

<p>@_msg</p>

@code {
    string _msg;

    protected override void OnInitialized()
    {
        base.OnInitialized();
        SC.OnChange += SubscribEvent;
    }

    public void SubscribEvent(string msg)
    {
        _msg = "Component_B received: " + msg;
    }

    public void Dispose()
    {
        SC.OnChange -= SubscribEvent;
    }
}

Component_C.razor

@inject IStateContainer SC
@implements IDisposable

<p>@_msg</p>

@code {
    string _msg;

    protected override void OnInitialized()
    {
        base.OnInitialized();
        SC.OnChange += SubscribEvent;
    }

    public void SubscribEvent(string msg)
    {
        _msg = "Component_C received: " + msg;
    }

    public void Dispose()
    {
        SC.OnChange -= SubscribEvent;
    }
}

参考文章

  1. ASP.NET Core Razor 组件:
    https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/?view=aspnetcore-6.0#component-parameters

  2. 数据绑定:
    https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/data-binding?view=aspnetcore-6.0#binding-with-component-parameters

  3. 级联值和参数:
    https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/cascading-values-and-parameters?view=aspnetcore-6.0

  4. ASP.NET Core Blazor 事件处理:
    https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0

  5. 内存中状态容器服务
    https://learn.microsoft.com/zh-cn/aspnet/core/blazor/state-management?view=aspnetcore-9.0&pivots=server#in-memory-state-container-service

  6. Blazor组件之间通信的3中方法
    https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/

  7. 了解级联值和级联参数
    https://chrissainty.com/understanding-cascading-values-and-cascading-parameters/

文章声明

  • 内容准确性:我会尽力确保所分享信息的准确性和可靠性,但由于个人知识有限,难免会有疏漏或错误。如果您在阅读过程中发现任何问题,请不吝赐教,我将及时更正。

posted on 2024-12-09 15:08  wubing7755  阅读(39)  评论(0编辑  收藏  举报