【发布时间】:2020-01-21 06:34:41
【问题描述】:
我有一个相当简单的组件,如下所示:
<form>
<div class="form-group">
<label for="sampleText">Sample Text</label>
<input type="text"
class="form-control"
id="sampleText"
aria-describedby="sampleTextHelp"
placeholder="Enter a text"
@bind="@Value" />
<small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small>
</div>
</form>
<p>You entered here: @Value</p>
@code {
[Parameter]
public string Value { get; set; }
}
我现在将它添加到这样的页面中:
<MVVMTest Value="@_value" />
<p>
You entered in MVVMTest:
@_value
</p>
@functions {
private string _value;
}
当我在输入字段中输入文本时,它会在 您在此处输入: 处正确更新,但不会传播到 *您在 MVVMTest 中输入”:
我必须做什么才能正确传播?
我可以考虑将Action<string> 连接为第二个[Parameter],当文本更改时我会在组件内触发它,但这似乎是一种迂回和迂回的方式。是必须这样做,还是有更好的方法?
跟进 1
Issac 的答案不起作用,因为它在@bind-value:oninput="@((e) => ValueChanged.Invoke(e.Value))" 和Cannot convert lambda expression to type 'object' because it is not a delegate type 上绊倒了。
我不得不以这种迂回的方式来做:
<form>
<div class="form-group">
<label for="sampleText">Sample Text</label>
<input type="text"
class="form-control"
id="sampleText"
aria-describedby="sampleTextHelp"
placeholder="Enter a text"
@bind="@Value" />
<small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small>
</div>
</form>
<p>You entered here: @Value</p>
@code {
private string _value;
[Parameter]
public string Value {
get => _value;
set {
if(Equals(value, _value)) {
return;
}
_value = value;
OnValueChanged.InvokeAsync(value).GetAwaiter().GetResult();
}
}
[Parameter]
public EventCallback<string> OnValueChanged { get; set; }
}
并像这样使用它:
@inject HttpClient http
@page "/test"
<div class="top-row px-4">
Test Page
</div>
<div class="content px-4">
<MVVMTest Value="@_value" OnValueChanged="@(v => _value = v)" />
<p>
You entered in MVVMTest:
@_value
</p>
</div>
@functions {
private string _value;
}
不漂亮,但它有效。我的“真实”组件更复杂,因为更改的值会触发对底层 ASP.net 核心服务的调用,因此我必须仔细检测谁更改了什么以避免无限循环。
如果 Blazor 支持类似 XAML/WPF 的 MVVM 肯定会更好...
跟进 2
我回到了 Issac 的解决方案,加上一个额外的演员,我得到了它的工作:
<form>
<div class="form-group">
<label for="sampleText">Sample Text</label>
<input type="text"
class="form-control"
id="sampleText"
aria-describedby="sampleTextHelp"
placeholder="Enter a text"
value="@Value"
oninput="@((Func<ChangeEventArgs, Task>)(async e => await OnValueChanged.InvokeAsync(e.Value as string)))" />
<small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small>
</div>
</form>
<p>You entered here: @Value</p>
@code {
private string _value;
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> OnValueChanged { get; set; }
}
用法:
<MVVMTest Value="@_value" OnValueChanged="@(v => _value = v)" />
<p>
You entered in MVVMTest:
@_value
</p>
@functions {
private string _value;
}
这已经是 Blazor 中的一个已知问题。我上周已经绊倒了,我也发布了解决方法in the VS developer forum。
【问题讨论】:
-
请注意,使用 EventCallback 会在父组件中引发 StateHasChanged(),这将触发所有父组件的子组件的渲染,因此除非您希望每个组件在每次击键或数据更改时都重新渲染- 可以考虑您的架构 - 查看 ShouldRender() 覆盖并实现代码以确保每个组件仅在需要时呈现,或者考虑使用不依赖 UI 传播状态更改的状态管理模式。
-
这也是一个好点。是否也可以只重新渲染单个组件?然后,我可以使用
Action或Func而不是EventCallback,并让组件决定何时重新渲染自己。 -
是的,如果你使用Action/Func而不是EventCallback,那么父级可以决定是否调用StateHasChanged,但是如果调用了,你仍然会触发所有子级的重新渲染。这就是 UI 之外的状态管理的亮点 - 无论您选择哪种方法,组件都应该能够“注册”自己,以便在数据更改时收到通知,以便它可以自己调用 StateHasChanged。
-
这可以像 DI 中的简单状态服务一样简单,它具有在数据更改时触发的操作/事件,或者它可以变得像您喜欢的任何状态管理库一样复杂。或者您可以使用 ShouldRender() 作为选择退出重新渲染的被动方式。在小型个人项目中,我发现实现事件的状态服务最简单。
-
等一下!您提到了避免无限循环。我偶然发现了那个确切的问题。简单案例:我只是将输入值从复选框向上传递到父元素。这会导致无限循环和崩溃。我有类似的元素,当我在 JS 中读取值并且我有一个阻止循环的标志时,因为我知道来自 JS 的调用是我应该调用
ValueChanged的唯一情况。现在我没有这个区别,那我该如何实现死循环预防呢?
标签: c# mvvm data-binding blazor