Blazor 组件需要修改父组件的值

5
我有这两个组件在我的 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; }
}

当我点击“Set DisplayedText”按钮时,Component1中的p元素中的文本应该更改为testing,但它没有更改。我该如何修复这个问题?
3个回答

10

@Merlin04,这是滥用和误用级联值特性。通常情况下,父组件通过组件参数与其子组件通信。

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

不正确...

以下代码片段展示了一种更好的解决方案,基于您所做的内容,虽然它不是最优的,因为您的代码和我的代码都是通过方法更新属性的值,但事实上,我们应该直接更改属性的值,而不是通过一个中介代码(即方法)。

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“委托”时获得的额外好处。
希望这可以帮助到您...

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

8

Merlin04,以下代码片段展示了您如何实现它。请注意,这只是一个非常简单的示例,但它展示了当需要在远程组件之间进行通信时应该如何编写代码。

以下是代码,请复制并运行它,如果您有更多问题,请不要犹豫问。

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类型的事件委托,并在组件3中的文本框中输入文本时从属性的set访问器中调用它。触发此委托会导致由Component3输入的文本显示在Component2的父级——Index组件中(请参见下面的代码)。

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版本能否做到同样的事情?顺便说一下,我已经为你的答案点赞了,这也是我处理这个问题的方式。 - Nik P
我就是这么想的,谢谢你澄清。是时候再次熟悉Action和Func并节省一些打字时间了。 - Nik P
@enet非常感谢您的帮助,我已经成功实现了它。不过我有一个问题,为什么StateHasChanged需要在InvokeAsync里面? - Merlin04
@Merlin04,我无法比Blazor团队做得更好,所以这里有一个链接,其中解释了该问题。如果您有任何问题,请随时问我:https://learn.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#invoke-component-methods-externally-to-update-state - enet
2
感谢 @enet 提供的示例,我一直在尝试寻找在 Blazor 中调用祖先的方法。在阅读了您的示例后,我找到了这个微软链接:“内存状态容器服务” https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-5.0&pivots=webassembly#in-memory-state-container-service-wasm,以及这个第三方库 https://jonhilton.net/blazor-state-management/。 - kite
显示剩余2条评论

0

请查看enet的答案

你不能从子元素更新级联值。

相反,可以在父元素(Component1)中创建一个方法来设置该值:

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

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接