在 Blazor 中的两个子组件之间共享数据的最佳方式
Posted
技术标签:
【中文标题】在 Blazor 中的两个子组件之间共享数据的最佳方式【英文标题】:Best way to share data between two child components in Blazor 【发布时间】:2020-05-27 11:22:12 【问题描述】:我有这个代码。
<ParentComponent>
<ChildComponet>
@renderFragment
</ChildComponent>
<ChildComponetn>
<GridComponent Data="@dataList"/>
</ChildComponent>
</ParentComponent>
其中@renderFragment 是动态渲染组件,而网格组件是一些数据的列表,其中包含“添加新”、“编辑记录”、“删除”等操作。
如果我们点击“添加新”,添加新记录的表单会在@renderFragment 中动态打开,我们想在提交表单后刷新网格数据,但我们不知道如何在两个子组件之间共享一些数据。编辑表单也是如此,当编辑某些记录时,我们需要刷新网格组件以显示编辑的数据。 如果需要更多代码和数据,请评论。
【问题讨论】:
通过父级的事件,或数据服务,或更高级别的级联参数(如共享父级) Blazor 本质上是React#
。共享信息的方式与 React 相同 - 状态应由父级持有,组件应触发对该状态的更新。其他子组件将根据该状态更改进行更新
【参考方案1】:
您可以定义一个实现状态模式和通知器模式的类服务来处理您的对象的状态,将状态传递给对象,并通知订阅者对象的更改。
这是此类服务的简化示例,它使父组件能够与其子组件进行通信。
NotifierService.cs
public class NotifierService
private readonly List<string> values = new List<string>();
public IReadOnlyList<string> ValuesList => values;
public NotifierService()
public async Task AddTolist(string value)
values.Add(value);
await Notify?.Invoke();
public event Func<Task> Notify;
Child1.razor
@inject NotifierService Notifier
@implements IDisposable
<div>User puts in something</div>
<input type="text" @bind="@value" />
<button @onclick="@AddValue">Add value</button>
@foreach (var value in Notifier.ValuesList)
<p>@value</p>
@code
private string value get; set;
public async Task AddValue()
await Notifier.AddTolist(value);
public async Task OnNotify()
await InvokeAsync(() =>
StateHasChanged();
);
protected override void OnInitialized()
Notifier.Notify += OnNotify;
public void Dispose()
Notifier.Notify -= OnNotify;
Child2.razor
@inject NotifierService Notifier
<div>Displays Value from service and lets user put in new value</div>
<input type="text" @bind="@value" />
<button @onclick="@AddValue">Set Value</button>
@code
private string value get; set;
public async Task AddValue()
await Notifier.AddTolist(value);
用法
@page "/"
<p>
<Child1></Child1>
</p>
<p></p>
<p>
<Child2></Child2>
</p>
Startup.ConfigureServices
services.AddScoped<NotifierService>();
希望这会有所帮助...
【讨论】:
还有一件事,你可以创建一个局部变量,并在 OnNotify 方法中将其绑定到 NotifierService,如下所示:public async Task OnNotify() await InvokeAsync(() => yourLocalVariable = Notifier.ValuesList; StateHasChanged(); );
看起来通信发生在两个组件之间的同一页面上 - 知道在两个浏览器选项卡上打开的两个页面和相同的用户范围是否应该相同?看起来像创建了两个不同的 NotifierService 实例,因此当从另一个页面发生更新时不会调用订阅的方法?!【参考方案2】:
有几种方法可以做到这一点,我刚刚学习了一个非常酷的方法,使用 Singleton 类。
我有一个组件,用于在我的聊天中向其他用户发送消息,称为 SubscriptionService,但您可以使用任何类。
将此注入添加到您的两个组件中:
@inject Services.SubscriberService SubscriberService
#region using statements
using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
#endregion
namespace BlazorChat.Services
#region class SubscriberService
/// <summary>
/// This class is used to subscribe to services, so other windows get a notification a new message
/// came in.
/// </summary>
public class SubscriberService
#region Private Variables
private int count;
private Guid serverId;
private List<SubscriberCallback> subscribers;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'SubscriberService' object.
/// </summary>
public SubscriberService()
// Create a new Guid
this.ServerId = Guid.NewGuid();
Subscribers = new List<SubscriberCallback>();
#endregion
#region Methods
#region BroadcastMessage(SubscriberMessage message)
/// <summary>
/// This method Broadcasts a Message to everyone that ins't blocked.
/// Note To Self: Add Blocked Feature
/// </summary>
public void BroadcastMessage(SubscriberMessage message)
// if the value for HasSubscribers is true
if ((HasSubscribers) && (NullHelper.Exists(message)))
// Iterate the collection of SubscriberCallback objects
foreach (SubscriberCallback subscriber in Subscribers)
// if the Callback exists
if ((subscriber.HasCallback) && (subscriber.Id != message.FromId))
// to do: Add if not blocked
// send the message
subscriber.Callback(message);
#endregion
#region GetSubscriberNames()
/// <summary>
/// This method returns a list of Subscriber Names ()
/// </summary>
public List<string> GetSubscriberNames()
// initial value
List<string> subscriberNames = null;
// if the value for HasSubscribers is true
if (HasSubscribers)
// create the return value
subscriberNames = new List<string>();
// Get the SubscriberNamesl in alphabetical order
List<SubscriberCallback> sortedNames = Subscribers.OrderBy(x => x.Name).ToList();
// Iterate the collection of SubscriberService objects
foreach (SubscriberCallback subscriber in sortedNames)
// Add this name
subscriberNames.Add(subscriber.Name);
// return value
return subscriberNames;
#endregion
#region Subscribe(string subscriberName)
/// <summary>
/// method returns a message with their id
/// </summary>
public SubscriberMessage Subscribe(SubscriberCallback subscriber)
// initial value
SubscriberMessage message = null;
// If the subscriber object exists
if ((NullHelper.Exists(subscriber)) && (HasSubscribers))
// Add this item
Subscribers.Add(subscriber);
// return a test message for now
message = new SubscriberMessage();
// set the message return properties
message.FromName = "Subscriber Service";
message.FromId = ServerId;
message.ToName = subscriber.Name;
message.ToId = subscriber.Id;
message.Data = Subscribers.Count.ToString();
message.Text = "Subscribed";
// return value
return message;
#endregion
#region Unsubscribe(Guid id)
/// <summary>
/// This method Unsubscribe
/// </summary>
public void Unsubscribe(Guid id)
// if the value for HasSubscribers is true
if ((HasSubscribers) && (Subscribers.Count > 0))
// attempt to find this callback
SubscriberCallback callback = Subscribers.FirstOrDefault(x => x.Id == id);
// If the callback object exists
if (NullHelper.Exists(callback))
// Remove this item
Subscribers.Remove(callback);
// create a new message
SubscriberMessage message = new SubscriberMessage();
// set the message return properties
message.FromId = ServerId;
message.FromName = "Subscriber Service";
message.Text = callback.Name + " has left the conversation.";
message.ToId = Guid.Empty;
message.ToName = "Room";
// Broadcast the message to everyone
BroadcastMessage(message);
#endregion
#endregion
#region Properties
#region Count
/// <summary>
/// This property gets or sets the value for 'Count'.
/// </summary>
public int Count
get return count;
set count = value;
#endregion
#region HasSubscribers
/// <summary>
/// This property returns true if this object has a 'Subscribers'.
/// </summary>
public bool HasSubscribers
get
// initial value
bool hasSubscribers = (this.Subscribers != null);
// return value
return hasSubscribers;
#endregion
#region ServerId
/// <summary>
/// This property gets or sets the value for 'ServerId'.
/// </summary>
public Guid ServerId
get return serverId;
set serverId = value;
#endregion
#region Subscribers
/// <summary>
/// This property gets or sets the value for 'Subscribers'.
/// </summary>
public List<SubscriberCallback> Subscribers
get return subscribers;
set subscribers = value;
#endregion
#endregion
#endregion
对于我的聊天应用程序,我希望它可用于所有实例,因此在 Startup.cs 的配置服务方法中,添加一个 Sington:
services.AddSingleton<SubscriberService>();
使其仅可用于此浏览器实例:
services.AddScoped(SubscriberService);
现在,您可以从这两个组件调用方法或获取注入类的属性:
SubscriptionService.GetSubscribers();
或者,如果您更喜欢界面,我写了一篇关于此的博客文章,我不想复制文本:
https://datajugglerblazor.blogspot.com/2020/01/how-to-use-interfaces-to-communicate.html
注入方式非常酷,因为您的整个应用程序可以与其他用户实例进行通信以进行聊天。
【讨论】:
以上是关于在 Blazor 中的两个子组件之间共享数据的最佳方式的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在 Blazor 中为父组件和子组件指定两个不同的类型参数值?