我有一个Blazor页面,其中包含两个组件。一个组件具有一个按钮,单击该按钮可生成一个随机数。另一个组件的文本区域应显示生成的随机数。
<h1>Parent Page</h1>
<ProvideNumberComponent />
<DisplayNumberComponent />
@code {
}
<h3>Provides Number</h3>
<button class="btn btn-primary" @onclick="CalculateNumber">Provide Number</button>
@code {
private void CalculateNumber(MouseEventArgs e)
{
Random rnd = new Random();
Int32 nextNumber = rnd.Next();
}
}
<h3>Displays number</h3>
<textarea cols="9" rows="1" readonly style="font-family:monospace;" />
@code {
}
从计算同级组件中获得显示在显示同级组件中的数字的最干净方法是什么?
我的代码的问题是,每次单击按钮时都会实例化Random对象,而不是在初始化时实例化一次。通过将Random对象放在单例服务类中并将其注入计算组件中,是否可以最好地解决此问题?
有很多方法可以做到这一点。
也许还有更多。
如果我是您,我将数字保留在父组件中,将值传递给显示它的子组件,并将回调传递给生成它的组件。因此,子组件不会相互依赖。
<h1>Parent Page</h1>
<ProvideNumberComponent Number="@Number" />
<DisplayNumberComponent OnGenerateNumber="@HandleGenerateNumber" />
@code {
int Number { get; set; }
void HandleGenerateNumber(int newNumber) {
Number = newNumber;
}
}
<h3>Provides Number</h3>
<button class="btn btn-primary" @onclick="CalculateNumber">Provide Number</button>
@code {
[Parameter]
public EventCallback<int> OnGenerateNumber { get; set; }
private void CalculateNumber(MouseEventArgs e)
{
Random rnd = new Random();
Int32 nextNumber = rnd.Next();
OnGenerateNumber.InvokeAsync(nextNumber);
}
}
<h3>Displays number</h3>
@Number
<textarea cols="9" rows="1" readonly style="font-family:monospace;" />
@code {
[Parameter]
public int Number { get; set; }
}
我认为,最好的解决方案是创建一个实现状态模式和通知程序模式的服务。以下代码描述了如何通过中介进行两个同级之间的通信
public class NotifierService
{
public NotifierService()
{
}
int rnd;
public int RandomNumber
{
get => rnd;
set
{
if (rnd != value)
{
rnd= value;
if (Notify != null)
{
Notify?.Invoke();
}
}
}
}
public event Func<Task> Notify;
}
添加此:services.AddScoped<NotifierService>();
@inject NotifierService Notifier
@implements IDisposable
<h3>Provides Number</h3>
<button class="btn btn-primary" @onclick="CalculateNumber">Provide
Number</button>
@code
{
private void CalculateNumber(MouseEventArgs e)
{
Random rnd = new Random();
Int32 nextNumber = rnd.Next();
Notifier.RandomNumber = nextNumber;
}
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
@inject NotifierService Notifier
@implements IDisposable
<hr />
<h3>Displays number</h3>
<textarea value cols="9" rows="1" readonly style="font-family:monospace;">
@Notifier.RandomNumber
</textarea>
@code {
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
当然,您可以在多个组件中注入和使用该服务,以及添加该服务可以提供的更多功能。通过事件处理程序实现通信可能会出现问题,除非它在父级和子级之间...
希望此方法有效...
我认为接口是执行此操作的最佳方法。
这是从我的Nuget包DataJugger.Blazor.Components中获得的>>
接口IBlazorComponent:
#region using statements using System.Collections.Generic; #endregion namespace DataJuggler.Blazor.Components.Interfaces { #region interface IBlazorComponent /// <summary> /// This interface allows communication between a blazor componetn and a parent component or page. /// </summary> public interface IBlazorComponent { #region Methods #region ReceiveData(Message message) /// <summary> /// This method is used to send data from a child component to the parent component or page. /// </summary> /// <param name="data"></param> void ReceiveData(Message message); #endregion #endregion #region Properties #region Name /// <summary> /// This property gets or sets the Name. /// </summary> public string Name { get; set; } #endregion #region Parent /// <summary> /// This property gets or sets the Parent componet or page for this object. /// </summary> public IBlazorComponentParent Parent { get; set; } #endregion #endregion } #endregion }
接口IBlazorComponentParent
#region using statements using System.Collections.Generic; #endregion namespace DataJuggler.Blazor.Components.Interfaces { #region interface IBlazorComponentParent /// <summary> /// This interface is used to host IBlazorComponent objects /// </summary> public interface IBlazorComponentParent { #region Methods #region FindChildByName(string name) /// <summary> /// This method is used to find a child component that has registered with the parent. /// </summary> /// <param name="name"></param> /// <returns></returns> IBlazorComponent FindChildByName(string name); #endregion #region ReceiveData(Message message) /// <summary> /// This method is used to send data from a child component to the parent component or page. /// </summary> /// <param name="data"></param> void ReceiveData(Message message); #endregion #region Refresh() /// <summary> /// This method will call StateHasChanged to refresh the UI /// </summary> void Refresh(); #endregion #region Register(IBlazorComponent component) /// <summary> /// This method is called by the Sprite to a subscriber so it can register with the subscriber, and /// receiver events after that. /// </summary> void Register(IBlazorComponent component); #endregion #endregion #region Properties #region Children /// <summary> /// This property gets or sets the value for Children. /// </summary> public List<IBlazorComponent> Children { get; set; } #endregion #endregion } #endregion }
有关用法,这是最相关的部分:
在您的组件中,它是IBlazorCompoent(子级),有一个Parent属性。
在组件中,您可以这样设置父对象:
<Login Parent=this></Login>
然后在您的组件中,像这样更改父属性:
[Parameter] public IBlazorComponentParent Parent { get { return parent; } set { // set the value parent = value; // if the Parent exists (Parent != null) { // Register with the parent Parent.Register(this); } } }
接下来,在实现IBlazorComponentParent的父组件中,为您的组件添加一个属性,并将Register方法更改为此:
// Login component reference public Login LoginComponent { get; set; } public void Register(IBlazorComponent component) { if (component is Login) { // Store the LoginComponent LoginComponent = component as Login; } else if (component is Join) { // Store the compoent SignUpComponent = component as Join; } }
现在,我的Login组件了解父级,而父级也了解Login,因此我可以像这样发送消息:
从孩子那里,发送一条简单的消息:
if (Parent != null) { Message message = new Message(); message.Text = "Some message"; Parent.SendMessage(message); }
或发送复杂的消息
// create a message DataJuggler.Blazor.Components.Message message = new DataJuggler.Blazor.Components.Message(); // Create the parameters to pass to the component NamedParameter parameter = new NamedParameter(); // Set the name parameter.Name = "PixelInformation Update"; parameter.Value = pixel; // Create a new collection of 'NamedParameter' objects. message.Parameters = new List<NamedParameter>(); // Add this parameter message.Parameters.Add(parameter); // Send this to the component ColorPickerComponent.ReceiveData(message);
然后在父级中接收消息:
public void ReceiveData(Message message) { // If the message object exists and has parameters if ((message != null) && (message.HasParameters)) { // if this a PixelInformation update from the Index page if (message.Parameters[0].Name == "PixelInformation Update") { // this is only relevant to my app, just showing an example of // \what I do with the data after it is received. // Set the SelectedPixel SelectedPixel = (PixelInformation) message.Parameters[0].Value; // Set the properties from the Pixel to display SetupColorPicker(); } } }
上面的代码在我的最新站点PixelDatabase.Net https://pixeldatabase.net中使用
如果有人需要,Nuget软件包代码都是开放源代码:
DataJuggler.Blazor.Componentshttps://github.com/DataJuggler/DataJuggler.Blazor.Components
我来自Windows Forms背景,所以我喜欢能够在这样的组件之间进行通信,但数据绑定并不总是有效。
this.Login.DoSomething(data);
您也可以像这样将父对象转换为特定类型:
public IndexPage ParentIndexPage { get { // cast the Parent object as an Index page return this.Parent as IndexPage; } }
因此您的孩子可以在父级上调用方法或设置属性,如果父级当然存在,那么请始终添加一个:
public bool HasParentIndexPage { get { // return true if the ParentIndexPage exists return (ParentIndexPage != null); } }
于是为了方便孩子使用:
// if the parent index page exists
if (HasParentIndexPage)
{
// Safely call your parent page
ParentIndexPage.SomeMethod();
}