Blazor如何用分部组件创建支持数据同步的表单
问题
我定义了如下Person类:
// Person.cs public class Person { public string Name { get; set; } public DateTime DateOfBirth { get; set; } public double Temperature { get; set; } public double Pulse { get; set; } public double Weight { get; set; } }
我希望通过复用分部表单组件的方式创建完整表单,父组件MyForm.razor的参考代码如下:
// MyForm.razor <h1>Currently editing: @Person.Name</h1> <EditForm EditContext="EditContext"> <PersonDefault /> <PersonHealthStats /> </EditForm> @code { private Person Person; private EditContext? EditContext; protected override void OnInitialized() { EditContext = new(Person); } }
请问应当如何正确实现上述两个分部子组件?
实现需求
PersonDefault组件需包含Name和DateOfBirth字段对应的输入控件PersonHealthStats组件需包含Temperature、Pulse、Weight字段对应的输入控件- 任意字段更新时,需同步修改父组件持有的
Person实例属性 Name字段更新时,需实时同步页面h1标签的显示内容
已尝试的方案
1. 将Person对象整体绑定到子组件
// MyForm.razor <PersonDefault @bind-Person="Person" /> // PersonDefault.razor <input @bind="Person.Name" /> @code { [Parameter] public Person Person { get; set; } [Parameter] public EventCallback<Person> PersonChanged { get; set; } }
该方案可以正常更新Person对象的属性值,但无法触发h1标签的内容同步刷新。
2. 为每个字段单独绑定参数
// MyForm.razor <PersonDefault @bind-Name="Person.Name" /> // PersonDefault.razor <input @bind="Name" /> @code { [Parameter] public EventCallback<string> NameChanged { get; set; } private string _Name; [Parameter] public string Name { get => _Name; set { if (_Name == value) return; _Name = value; NameChanged.InvokeAsync(value); } } }
该方案可以同时实现Person属性更新和h1标签内容同步,但随着组件内输入字段增多,会产生大量冗余代码。
编辑补充
我制作了一张示意图,以便更清晰地展示想要实现的效果:
实现方案
Blazor的EditForm组件会自动将持有的EditContext作为级联参数下发给所有后代组件,不需要手动给子组件传递模型对象,也不需要为每个字段单独编写绑定逻辑,具体实现如下:
1. 调整父组件MyForm.razor
初始化Person实例避免空引用,同时给EditContext注册字段变更事件,当检测到Name字段修改时触发组件重绘即可:
// MyForm.razor <h1>Currently editing: @Person.Name</h1> <EditForm EditContext="EditContext"> <PersonDefault /> <PersonHealthStats /> </EditForm> @code { private Person Person = new(); private EditContext? EditContext; protected override void OnInitialized() { EditContext = new(Person); // 监听字段变更 EditContext.OnFieldChanged += (_, e) => { // Name字段修改时触发当前组件重绘,更新h1内容 if (e.FieldIdentifier.FieldName == nameof(Person.Name)) { StateHasChanged(); } }; } }
2. 实现PersonDefault.razor子组件
接收级联的EditContext参数,直接从上下文获取父组件持有的Person实例,用Blazor原生输入组件绑定对应字段即可:
// PersonDefault.razor <div class="form-group"> <label>姓名:</label> <InputText @bind-Value="person.Name" class="form-control" /> </div> <div class="form-group"> <label>出生日期:</label> <InputDate @bind-Value="person.DateOfBirth" class="form-control" /> </div> @code { [CascadingParameter] private EditContext EditContext { get; set; } = default!; private Person person = default!; protected override void OnInitialized() { // 直接获取父组件持有的Person实例,修改操作会直接同步到父组件 person = (Person)EditContext.Model; } }
3. 实现PersonHealthStats.razor子组件
逻辑与PersonDefault一致,绑定健康类字段即可:
// PersonHealthStats.razor <div class="form-group"> <label>体温:</label> <InputNumber @bind-Value="person.Temperature" class="form-control" /> </div> <div class="form-group"> <label>脉搏:</label> <InputNumber @bind-Value="person.Pulse" class="form-control" /> </div> <div class="form-group"> <label>体重:</label> <InputNumber @bind-Value="person.Weight" class="form-control" /> </div> @code { [CascadingParameter] private EditContext EditContext { get; set; } = default!; private Person person = default!; protected override void OnInitialized() { person = (Person)EditContext.Model; } }
方案说明
- 所有子组件直接操作父组件持有的同一个Person实例,不存在数据拷贝,字段修改后天然同步
- 不需要为每个字段单独编写参数和EventCallback,没有冗余代码,新增字段只需要在对应子组件添加输入控件绑定即可
- 父组件通过监听EditContext的字段变更事件精准触发重绘,只有Name修改时才会刷新h1,性能开销极低
- 完全兼容Blazor原生表单的验证、输入提示等所有内置能力
内容的提问来源于stack exchange,提问作者Jacob Dachenhaus
相关产品推荐
相关产品推荐

