【问题标题】:Blazor component needs to modify value of parent componentBlazor 组件需要修改父组件的值
【发布时间】:2020-04-23 00:10:34
【问题描述】:

我的 Blazor 应用中有这两个组件:

Component1.razor:

<CascadingValue Value=this>
    <Component2/>
</CascadingValue>

<p>@DisplayedText</p>

@code {
    public string DisplayedText = string.Empty;
}

Component2.razor:

<button @onclick=@(e => { C1.DisplayedText = "testing"; })>Set DisplayedText</button>

@code {
    [CascadingParameter]
    public Component1 C1 { get; set; }
}

当我单击“设置 DisplayedText”按钮时,Component1 中的 p 元素中的文本应更改为 testing,但事实并非如此。我该如何解决这个问题?

【问题讨论】:

    标签: c# razor blazor


    【解决方案1】:

    @Merlin04,这是对级联值功能的滥用和误用。通常,父组件通过组件参数与其子组件进行通信。

    您不能从后代更新级联值。

    错了……

    下面的代码 sn-p 演示了一个更好的解决方案,基于你所做的,虽然它不是最优的,因为你的代码和我的代码也是从方法更新属性,事实上,我们应该改变属性的直接取值,而不是通过中介代码(即方法)

    Component2.razor

    <button @onclick="@(() => SetDisplayedText.InvokeAsync("testing"))">Set 
                                                     DisplayedText</button>
    
    @code {
       [Parameter]
       public EventCallback<string> SetDisplayedText { get; set; }
    }
    

    Component1.razor

    <Component2 SetDisplayedText="@SetDisplayedText"/>
    
    <p>@DisplayedText</p>
    
    @code {
        private string DisplayedText = string.Empty;
    
        public void SetDisplayedText(string newText)
       {
           DisplayedText = newText;
       }
    }
    

    请注意,不需要调用 StateHasChanged 方法,因为这是您在使用 EventCallback 'delegate' 时获得的好处

    希望这会有所帮助...

    【讨论】:

    • 有道理,如果我有一个 Component3 是 Component2 的子级,并且我需要从 Component3 更新 DisplayedText,这仍然是最好的方法吗?
    • 不,使用事件处理程序仅适用于父子关系。在这种情况下,您需要在 component1 到 component3 之间有一个中介。一个可行的解决方案是创建一个应该注入到组件 1 和组件 3 中的服务。此服务应定义一个公共字符串属性来存储从 component3 传递给它的文本。它还应该定义在属性更改时应该引发的事件委托(从组件 3 传递值),并且该事件的订阅者,例如组件 1,将被通知这一事实(新值已到达),
    • 并通过从服务中定义的属性读取值或以事件参数的形式传递值来访问此值。这对你来说可能是一个很好的锻炼。尝试一下。如果您需要帮助,我在这里......
    • 服务会像普通类一样创建吗?我将如何添加委托?
    【解决方案2】:

    Merlin04,下面的代码 sn -p 演示了如何做到这一点。请注意,这实际上是一个非常简单的示例,但它显示了当需要在远程组件之间进行通信时应该如何编码。

    这是代码,复制并运行它,如果您有更多问题,请随时提问。

    MessageService.cs

     public class MessageService
    {
        private string message;
        public string Message
        {
            get => message;
            set
            {
                if (message != value)
                {
                    message = value;
                    if (Notify != null)
                    {
                         Notify?.Invoke();
                    }
    
                }
            }
        }
    
        public event Action Notify;
    }
    

    注意:服务是一个普通的类...它为其他对象提供服务,应该在 Startup.ConfigureServices 方法中将它添加到 DI 容器中,以使其可供请求的客户端使用。在 ConfigureServices 方法中添加:

     services.AddScoped<MessageService>();
    

    注意:如您所见,我定义了一个 Action 类型的事件委托,当用户在 Component3 中的文本框中键入文本时,它会从属性的 set 访问器调用。触发此委托会导致 Components3 输入的文本显示在 Index 组件中,该组件是 Component2 的父级(参见下面的代码)。

    Index.razor

    @page "/"
    
    @inject MessageService MessageService
    @implements IDisposable
    
    <p>I'm the parent of Component2. I've got a message from my grand child: 
                                                @MessageService.Message</p>
    
    <Component2 />
    
    @code {
    protected override void OnInitialized()
    {
        MessageService.Notify += OnNotify;
    }
    
    public void OnNotify()
    {
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
     }
    
     public void Dispose()
     {
        MessageService.Notify -= OnNotify;
     }
    }
    

    注意我们直接绑定MessageService.Message属性,但是必须调用StateHasChanged方法来刷新文本的显示。

    Component2.razor

    <h3>Component2: I'm the parent of component three</h3>
    <Component3/>
    
    @code {
    
    }
    

    Component3.razor

    @inject MessageService MessageService
    
    <p>This is component3. Please type a message to my grand parent</p>
    <input placeholder="Type a message to grandpa..." type="text" 
    @bind="@MessageService.Message" @bind:event="oninput" />
    

    请注意,在 Component3 中,我们将 MessageService.Message 绑定到一个文本框,并且每次按下键盘时都会发生绑定(输入事件与更改 事件)。

    仅此而已,希望对您有所帮助,请随时提出任何问题。

    【讨论】:

    • +1 - 声明public event Action Notify - 出于某种原因,我从未连接过它并一直声明委托,然后声明基于委托的事件。你的方法有什么缺点吗?我知道声明的委托方法会限制方法签名,您的 Action 版本可以这样做吗?顺便提一下你的答案,这也是我处理这个问题的方式。
    • 事实上,我是自动完成的,没有任何想法。如果我理解你是正确的,你的意思是你通常做这样的事情: public delegate void MyAction();公共事件 MyAction 通知;这是合法的并且应该可以工作......您在这里所做的是为没有参数且不返回值的方法定义自定义委托。这正是 Action 定义的方式...约束是您定义委托的结果。
    • 通过定义公共委托 void MyAction(T obj),您可以约束您的委托封装具有单个参数且不返回值的方法。这正是预定义的委托 Action
    • 这就是我的想法,感谢您的澄清。是时候重温 Action 和 Func 了,省点打字时间。
    • 感谢@enet 提供此示例,我试图在 blazor 中寻找后代调用祖先的方法。这是我在阅读您的示例后找到的微软链接:“内存状态容器服务”docs.microsoft.com/en-us/aspnet/core/blazor/… 和这个用于第三方库jonhilton.net/blazor-state-management
    【解决方案3】:

    查看 enet 的答案而不是这个

    您不能从后代更新级联值。

    相反,在父级(Component1)中创建一个方法来设置值:

    public void SetDisplayedText(string newText) {
        DisplayedText = newText;
        StateHasChanged();
    }
    

    然后,您可以在后代中调用它:

    <button @onclick=@(e => { C1.SetDisplayedText("testing"); })>Set DisplayedText</button>
    

    【讨论】:

      猜你喜欢
      • 2021-09-07
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      • 2021-04-05
      • 2019-11-15
      • 2019-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多