原文链接:https://blazor-university.com/components/two-way-binding/

双向绑定

源代码[1]

注意: 如果您还没有这样做过,请在继续本节之前先执行单向绑定[2]中的步骤。

到目前为止,我们有一个包含嵌入组件的页面,并且我们组件的部分状态是从其宿主视图(Counter 页面)以名为 CurrentCounterValue 的参数的形式传递的。但是,如果我们还希望组件能够更新传递给它的状态呢?

如果您还不熟悉 EventCallback<T> 类,请阅读组件事件[3]。理想情况下,您还应该熟悉使用 Component 指令[4],并且为了更深入地了解绑定变体,您可能还希望熟悉浏览器 DOM 事件[5]

就像我们对 Counter 页面所做的那样,通过添加一个带有 onclick 事件的按钮来更新 CurrentCounterValue 的值。

<div>CurrentCounterValue in MyFirstComponent is @CurrentCounterValue
</div><button @onclick=UpdateCurrentCounterValue>Update</button>@code {[Parameter]public int CurrentCounterValue { get; set; }void UpdateCurrentCounterValue(){CurrentCounterValue++;}
}

问题在于默认情况下 [Parameter] 绑定是单向的。因此, Page.counter 的值将被赋给 MyFirstComponent.CurrentCounterValue ,因为父视图明确设置了它:

<MyFirstComponent CurrentCounterValue=@currentCount/>

但是,当 MyFirstComponent 中的属性发生更改时,组件会设置其状态的本地副本,而不是其父视图的状态。不仅如此,下次父状态更新时,它会将该值再次赋给 MyFirstComponent.CurrentCounterValue 并替换我们修改的值。

当状态改变时通知父组件

要解决此问题,我们需要告诉 Blazor 使用组件页面想要使用双向绑定。我们现在告诉 Blazor 绑定(即双向绑定)到该值,而不是简单地设置 CurrentCounterValue。要对参数使用双向绑定,只需在 HTML 属性前面加上文本 @bind-。这告诉 Blazor 它不仅应该将更改推送到组件,还应该观察组件的任何更改并相应地更新自己的状态。

<MyFirstComponent @bind-CurrentCounterValue=currentCount/>

注意: 将代码值(而不是常量)分配给参数时需要 @ 符号。以前我们的标记包含 CurrentCounterValue=@currentCount,但是一旦我们在参数名称前加上 @bind - currentCount 之前的 @ 符号就变得不必要了。

现在运行应用程序将在浏览器的控制台窗口中显示以下错误。

WASM:System.InvalidOperationException:“TwoWayBinding.Client.Components.MyFirstComponent”类型的对象没有与名称“CurrentCounterValueChanged”匹配的属性。

Blazor 中的双向绑定使用命名约定。如果我们想绑定一个名为 SomeProperty 的属性,那么我们需要一个名为 SomeProperyChanged 的事件回调。每当组件更新 SomeProperty 时,都必须调用此回调。

MyFirstComponent 中实现这一点

  • 添加 CurrentCounterValueChanged 事件回调。

  • UpdateCurrentCounterValue 方法从 void 更改为 async Task

  • 在增加 CurrentCounterValue 后,调用 CurrentCounterValueChanged 以通知使用者状态已更改。

<div>CurrentCounterValue in MyFirstComponent is @CurrentCounterValue
</div><button @onclick=@UpdateCurrentCounterValue>Update</button>@code {[Parameter]public int CurrentCounterValue { get; set; }[Parameter]public EventCallback<int> CurrentCounterValueChanged { get; set; }async Task UpdateCurrentCounterValue(){CurrentCounterValue++;await CurrentCounterValueChanged.InvokeAsync(CurrentCounterValue);}
}

工作原理

如果我们打开 obj\Debug\netstandard2.0\Razor\Pages 中的 Counter.razor.gs 文件,我们可以看到 BuildRenderTree 方法如下所示:

builder.OpenComponent<...MyFirstComponent>(10);builder.AddAttribute(11, "CurrentCounterValue", ...TypeCheck<System.Int32>(...BindMethods.GetValue(currentCount))
);builder.AddAttribute(12, "CurrentCounterValueChanged",...TypeCheck<...EventCallback<System.Int32>>(...EventCallback.Factory.Create<System.Int32>(this,...EventCallback.Factory.CreateInferred(this,__value => currentCount = __value,currentCount)))
);
builder.CloseComponent();

注意: 源代码已重新格式化,为简洁起见,名称空间已替换为 ...。

  • 第 5 行 执行从 Counter.currentCountMyFirstComponent.CurrentCounterValue 的单向绑定。

  • 第 15 行 每当执行 MyFirstComponent.CurrentCounterValueChanged 时,就会更新 Counter.currentCount

绑定指令

源代码[6]

我们之前介绍了指令[7]——如果您不熟悉指令,请在继续之前阅读有关它们的部分。

我们之前介绍了指令和指令属性。在本节中,我们将通过演示如何使用双向绑定来为指令属性分配值。

快速回顾一下,指令是元素中以 @ 符号开头的标识符。例如

<h1 @ref=OurReferenceToThisElement>Hello</h1>

指令属性是以 @directive:attribute 形式提供给指令的附加信息。例如,应用于 @onclick 指令的 preventDefault 属性会阻止提交按钮实际提交表单。

<input type="submit" @onclick:preventdefault>

除此之外,还可以通过以下形式为某些指令属性赋值:

<h1 @directive:attribute="someValue">Hello</h1>

尽管没有理由特别将这些属性值限制为双向绑定,但碰巧的是,目前 Blazor 框架中唯一使用此功能的地方恰好是双向绑定,这就是本节放在双向绑定部分下方的原因。

入门

注意: 尽管为了简单起见,我们将在此处使用 HTML <input> 元素,但为了获得更丰富的用户体验(添加验证等),我建议在 <EditForm> 组件中使用 Blazor <Input*> 组件(InputDate 等)。这些在表单[8]一节中介绍。

首先,我们需要一个在 @code 部分中定义了以下成员的页面,因此我们需要绑定一些内容:

@code
{private string Name;private DateTime? DateOfBirth;private decimal? BankBalance;
}

标准双向绑定

首先,我们将从标准双向绑定到 Blazor 页面的 Name 成员开始。

<label>Name = @Name</label>
<input @bind-value=Name/>

前面标记的重要部分是 @bind-value=Name。这将为 <input> 元素上名为 value 的 HTML 属性设置双向绑定,并将其绑定到 Name 成员。

如果我们现在运行我们的应用程序,我们将看到上方的 Name = @Name 文本不会更改以反映我们在 <input> 中键入的内容,直到输入元素失去焦点或我们按下 Enter 键。

使用指令属性立即检测变化

@bind 指令有一个名为 event 的指令属性。设置此指令形式的值采用以下格式:

<input @bind-value:event="x"/>

“x”的有效值是 onchangeoninput

当没有指定 :event 的值时,onchange 是假定的默认值。这是我们在运行示例时看到的行为——绑定仅在控件失去焦点或用户按下回车键时发生。

oninput:event 的唯一其他可能值,它指示 Blazor  hook 到 HTML 元素的 JavaScript oninput 事件,并在每次触发事件时更新绑定成员。这会导致每次用户更改输入中的值时立即更新绑定成员。

注意: -value 是要绑定到的 HTML 属性或 Blazor 组件属性的名称。对于 HTML 元素,前导字母为小写,对于组件属性,前导字母为大写,指令名称和绑定目标名称由 - 符号分隔。

将以下标记添加到我们的页面并运行应用程序。

<label>Name = @Name</label>
<input @bind-value=Name @bind-value:event="oninput"/>

@bind-value:event="oninput" 是指示 Blazor 使用即时更改检测的关键。首先我们告诉 Blazor 我们要将输入框的 value HTML 属性绑定到我们的 Name 成员 (@bind-value=Name),然后我们告诉 Blazor hook 到 HTML 元素的 oninput 事件,这样每次绑定都会立即发生元素的值发生变化(@bind-value:event="oninput")。

*** 指定自定义绑定格式 通过为 @bind 指令的格式属性指定一个值来指定在用户界面中使用的自定义格式。

将以下标记添加到我们的页面并运行应用程序。

<label>Date of birth = @DateOfBirth?.ToString("MMMM d, yyyy")</label>
<input @bind-value=DateOfBirth @bind-value:format="yyyy-MM-dd"/>

当应用程序运行时,输入 ISO 格式的日期(例如 1969-07-21)。虽然日期在 <label> 中显示为 July 21, 1969,但 <input> 控件以我们在 @bind-value:format="yyyy-MM-dd" 中指定的 ISO 显示它。

注意: 输入的任何与指定格式不匹配的值都将被丢弃。因此,我们不能设置 @bind-value:event="oninput",因为 Blazor 会尝试在每次按键时解析输入,但输入的值不可能在单次按键后有效,因此输入值将干脆消失。这是我建议在编辑数据时在 EditForm[9] 中使用 Blazor <Input*> 组件的原因之一,因为这使我们能够使用诸如 <InputDate> 之类的组件。

如 Descending from InputBase[10] 部分所述,Blazor 输入组件具有一对互补的受保护方法,用于将绑定值转换为字符串和从字符串转换为字符串。

工作原理

@bind 指令不会添加代码来直接绑定到我们的成员,而是简单地将其转换为字符串值/从字符串值转换。相反,它通过 BindConverter 重定向当前值的表示和输入值的解析。

如果我们查看 Blazor 为单向绑定(例如 class=@OurCssClass)生成的 .cs 文件,我们会看到 C# 看起来像这样(为简洁起见进行了编辑)。

protected override void BuildRenderTree(RenderTreeBuilder __builder)
{_builder.AddAttribute(1, "class", OurCssClass);
}

现在,如果我们查看生成的双向绑定文件,我们将看到类似于以下(有删减)代码的内容,用于显示该值:

protected override void BuildRenderTree(RenderTreeBuilder __builder)
{_builder.AddAttribute(1, "value",...BindConverter.FormatValue(Name));

以及类似于以下(有删减)代码,用于将用户输入转换回绑定成员。

__builder.AddAttribute(11, "onchange",...EventCallback.Factory.CreateBinder(this, __value => Name = __value, Name));
}

代码 hook 到 HTML onchange 事件,然后在事件触发时设置我们的成员值。

设置 @bind-value:format 指令属性值时的不同之处在于我们提供的格式在生成的代码中传递给了 BindConverter.FormatEventCallback.Factory.CreateBinder

...BindConverter.FormatValue(Name, format: "yyyy-MM-dd");
// and
CreateBinder(...., format: "yyyy-MM-dd");

指定自定义 culture

世界上的人们有不同的习俗和文化,这是使世界变得如此有趣的原因之一。不幸的是,这也是使编写软件更加困难的原因之一。

将以下标记添加到我们的页面:

<label>Bank balance = @BankBalance</label>
<input @bind-value=BankBalance @bind-value:culture=Turkish/>

并确保将以下成员添加到 @code 部分的页面中:

private CultureInfo Turkish = CultureInfo.GetCultureInfo("tr-TR");

输入值 12.42 可能会期望余额超过 12 土耳其里拉,但正如我们所见,我们只是不小心给了某人 1,242 土耳其里拉。当然,居住在土耳其的人会知道要输入 12,42,但这凸显了当我们的应用程序打算在其他国家/地区使用时正确指定文化的必要性。

format 指令属性一样,指定的 @bind-value:culture 将作为命名(可选)值传递给 BinderBindConverter

如果您还没有听说过土耳其测试,那么我建议您阅读这篇优秀的文章[11]

参考资料

[1]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/Components/TwoWayBinding

[6]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/Components/BindingDirectives

[11]

这篇优秀的文章: http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html

Blazor University (7)组件 — 双向绑定相关推荐

  1. [vue] vue父子组件双向绑定的方法有哪些?

    [vue] vue父子组件双向绑定的方法有哪些? 1.利用对象的引用关系来实现 2.父子组件之间的数据传递 3.使用.sync修饰符 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但 ...

  2. Vue父组件访问子组件属性和方法、父子组件双向绑定(两种方法)

    Vue父组件访问子组件属性和方法.父子组件双向绑定(两种方法) 1. 使用vue-cli创建项目 目录结构如下图: 2. 编写代码 src/components/HelloWorld.vue < ...

  3. vue 父链和子组件索引_vue子组件和父组件双向绑定的几种方案

    v-model案例 模仿v-model实现案例 我是一串要和内部名字联动的一串文字(父组件) 父组件改变值带动(父组件)点一下试试 .sync方案实现案例 这是父组件的东西.利用这个框改变值,看看有没 ...

  4. Vue 父子组件双向绑定传值

    最简单的双向绑定(单文件中)就是表单元素的v-model了,如果同时设置v-model和value,value属性无效. 自定义v-model:(不推荐) child: <template> ...

  5. vue组件双向绑定.sync修饰符的一个坑

    我们知道组件是单项的,但是有时候需要双向,这时候我们可以使用.sync修饰符,但今天遇到一个坑,一直不成功,花了半小时试出来的.... 在编程的时候我们很习惯冒号后面跟着空格.而.sync双向绑定需要 ...

  6. angular : 自定义组件双向绑定 [(ngModel)]

    NG_VALUE_ACCESSOR 用于为表单控件提供 ControlValueAccessor interface ControlValueAccessor {writeValue(obj: any ...

  7. vue2.x版本+element-ui2.15+版本实现只能输入数字的ip输入框,功能样式借鉴windows,与父组件双向绑定

    目录 1.双向绑定 2.:oninput动态绑定和@input事件处理 3.主要功能 4.自定义组件代码 1.双向绑定 实现双向绑定在vue2.x版本中是使用v-model实现的,本文子类中的主要代码 ...

  8. vue实现组件双向绑定

    // test.vue<template><div><input v-model="val"><testComponent v-model ...

  9. angular中自定义组件实现双向绑定

    使用get,set关键字 get返回父组件变化后的值 set为组件值产生变化后父组件改变 import {Input, Output } from '@angular/core'; ........ ...

最新文章

  1. EM上的按钮是方框的问题
  2. [No000035]操作系统Operating System之OS Interface操作系统接口
  3. nexus-3.6.0-02-unix.tar.gz安装(Centos下),maven setting.xml配置案例,项目root的pom.xml配置,parent-pom的pom.xml配置案例
  4. asp.net出现:当前标识(NT AUTHORITY\NETWORK SERVICE)没有对' '的写访问权限。
  5. 快照速度_网络推广——网络推广专员如何看待网站快照更新快慢问题?
  6. C++校招常见面试题(2019年校招总结)
  7. 【英语学习】【医学】Unit 06 Urine Formation
  8. 一个简单的VC++案例:显示年月日
  9. 【气动学】基于matlab RBF神经网络控制卫星轨道和姿态【含Matlab源码 377期】
  10. STM32 F7xx + LAN8720+LWIP1.4.1调试坑点记录
  11. python 进化树_SCHISM 构建克隆进化树
  12. Text B:What’s wrong with copying?剽窃何罪
  13. 小票打印机ESC/POS命令集
  14. 关于localhost404打不开
  15. 中国保险中介行业市场规模调研及投资可行性研究报告2022-2027年
  16. Python之暴力破解SSH
  17. 通达OA软件测试自学,通达OA工作流程及快速入门手册.doc
  18. NXP SPIFI(QSPI)应用详解与程序固件分散加载
  19. 经济学计算机考什么,计算机专业考人大经济学复习经验谈
  20. 【量化笔记】Markowitz模型的python实现

热门文章

  1. tcp有限状态机分析
  2. mysql 5.6.31 winx64_详解介绍MySQL5.6.31winx64.zip安装配置的图文教程
  3. 2019.04.24笔记
  4. wampServer配置WWW根目录遇到的坑
  5. C# 对Datatable排序
  6. QM课程02-外部功能
  7. 微软将终止免费的条码标签服务
  8. 60佳优秀的 Photoshop 网页制作教程【下篇】
  9. 记一次理想浪漫的毕旅
  10. 给Teams消息附加图片的三种方式