在 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(() =&gt; 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 中为父组件和子组件指定两个不同的类型参数值?

Blazor 组件之间使用 EventCallback 进行通信

Blazor WASM 在组件之间传递值

如何在两个子 Angular2 组件之间共享父 HTML?

如何从 blazor 中的子组件获取属性

在同一层级子组件之间共享存储更改事件