Blazor University (7)组件 — 双向绑定
原文鏈接:https://blazor-university.com/components/two-way-binding/
雙向綁定
源代碼[1]
注意: 如果您還沒有這樣做過,請在繼續(xù)本節(jié)之前先執(zhí)行單向綁定[2]中的步驟。
到目前為止,我們有一個包含嵌入組件的頁面,并且我們組件的部分狀態(tài)是從其宿主視圖(Counter 頁面)以名為 CurrentCounterValue 的參數(shù)的形式傳遞的。但是,如果我們還希望組件能夠更新傳遞給它的狀態(tài)呢?
如果您還不熟悉 EventCallback<T> 類,請閱讀組件事件[3]。理想情況下,您還應(yīng)該熟悉使用 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++;} }問題在于默認(rèn)情況下 [Parameter] 綁定是單向的。因此, Page.counter 的值將被賦給 MyFirstComponent.CurrentCounterValue ,因為父視圖明確設(shè)置了它:
<MyFirstComponent?CurrentCounterValue=@currentCount/>但是,當(dāng) MyFirstComponent 中的屬性發(fā)生更改時,組件會設(shè)置其狀態(tài)的本地副本,而不是其父視圖的狀態(tài)。不僅如此,下次父狀態(tài)更新時,它會將該值再次賦給 MyFirstComponent.CurrentCounterValue 并替換我們修改的值。
當(dāng)狀態(tài)改變時通知父組件
要解決此問題,我們需要告訴 Blazor 使用組件頁面想要使用雙向綁定。我們現(xiàn)在告訴 Blazor 綁定(即雙向綁定)到該值,而不是簡單地設(shè)置 CurrentCounterValue。要對參數(shù)使用雙向綁定,只需在 HTML 屬性前面加上文本 @bind-。這告訴 Blazor 它不僅應(yīng)該將更改推送到組件,還應(yīng)該觀察組件的任何更改并相應(yīng)地更新自己的狀態(tài)。
<MyFirstComponent?@bind-CurrentCounterValue=currentCount/>注意: 將代碼值(而不是常量)分配給參數(shù)時需要 @ 符號。以前我們的標(biāo)記包含 CurrentCounterValue=@currentCount,但是一旦我們在參數(shù)名稱前加上 @bind - currentCount 之前的 @ 符號就變得不必要了。
現(xiàn)在運行應(yīng)用程序?qū)⒃跒g覽器的控制臺窗口中顯示以下錯誤。
WASM:System.InvalidOperationException:“TwoWayBinding.Client.Components.MyFirstComponent”類型的對象沒有與名稱“CurrentCounterValueChanged”匹配的屬性。Blazor 中的雙向綁定使用命名約定。如果我們想綁定一個名為 SomeProperty 的屬性,那么我們需要一個名為 SomeProperyChanged 的事件回調(diào)。每當(dāng)組件更新 SomeProperty 時,都必須調(diào)用此回調(diào)。
在 MyFirstComponent 中實現(xiàn)這一點
添加 CurrentCounterValueChanged 事件回調(diào)。
將 UpdateCurrentCounterValue 方法從 void 更改為 async Task。
在增加 CurrentCounterValue 后,調(diào)用 CurrentCounterValueChanged 以通知使用者狀態(tài)已更改。
工作原理
如果我們打開 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 行 執(zhí)行從 Counter.currentCount 到 MyFirstComponent.CurrentCounterValue 的單向綁定。
第 15 行 每當(dāng)執(zhí)行 MyFirstComponent.CurrentCounterValueChanged 時,就會更新 Counter.currentCount。
綁定指令
源代碼[6]
我們之前介紹了指令[7]——如果您不熟悉指令,請在繼續(xù)之前閱讀有關(guān)它們的部分。
我們之前介紹了指令和指令屬性。在本節(jié)中,我們將通過演示如何使用雙向綁定來為指令屬性分配值。
快速回顧一下,指令是元素中以 @ 符號開頭的標(biāo)識符。例如
<h1?@ref=OurReferenceToThisElement>Hello</h1>指令屬性是以 @directive:attribute 形式提供給指令的附加信息。例如,應(yīng)用于 @onclick 指令的 preventDefault 屬性會阻止提交按鈕實際提交表單。
<input?type="submit"?@onclick:preventdefault>除此之外,還可以通過以下形式為某些指令屬性賦值:
<h1?@directive:attribute="someValue">Hello</h1>盡管沒有理由特別將這些屬性值限制為雙向綁定,但碰巧的是,目前 Blazor 框架中唯一使用此功能的地方恰好是雙向綁定,這就是本節(jié)放在雙向綁定部分下方的原因。
入門
注意: 盡管為了簡單起見,我們將在此處使用 HTML <input> 元素,但為了獲得更豐富的用戶體驗(添加驗證等),我建議在 <EditForm> 組件中使用 Blazor <Input*> 組件(InputDate 等)。這些在表單[8]一節(jié)中介紹。
首先,我們需要一個在 @code 部分中定義了以下成員的頁面,因此我們需要綁定一些內(nèi)容:
@code {private?string?Name;private?DateTime??DateOfBirth;private?decimal??BankBalance; }標(biāo)準(zhǔn)雙向綁定
首先,我們將從標(biāo)準(zhǔn)雙向綁定到 Blazor 頁面的 Name 成員開始。
<label>Name?=?@Name</label> <input?@bind-value=Name/>前面標(biāo)記的重要部分是 @bind-value=Name。這將為 <input> 元素上名為 value 的 HTML 屬性設(shè)置雙向綁定,并將其綁定到 Name 成員。
如果我們現(xiàn)在運行我們的應(yīng)用程序,我們將看到上方的 Name = @Name 文本不會更改以反映我們在 <input> 中鍵入的內(nèi)容,直到輸入元素失去焦點或我們按下 Enter 鍵。
使用指令屬性立即檢測變化
@bind 指令有一個名為 event 的指令屬性。設(shè)置此指令形式的值采用以下格式:
<input?@bind-value:event="x"/>“x”的有效值是 onchange 或 oninput。
當(dāng)沒有指定 :event 的值時,onchange 是假定的默認(rèn)值。這是我們在運行示例時看到的行為——綁定僅在控件失去焦點或用戶按下回車鍵時發(fā)生。
oninput 是 :event 的唯一其他可能值,它指示 Blazor ?hook 到 HTML 元素的 JavaScript oninput 事件,并在每次觸發(fā)事件時更新綁定成員。這會導(dǎo)致每次用戶更改輸入中的值時立即更新綁定成員。
注意: -value 是要綁定到的 HTML 屬性或 Blazor 組件屬性的名稱。對于 HTML 元素,前導(dǎo)字母為小寫,對于組件屬性,前導(dǎo)字母為大寫,指令名稱和綁定目標(biāo)名稱由 - 符號分隔。
將以下標(biāo)記添加到我們的頁面并運行應(yīng)用程序。
<label>Name?=?@Name</label> <input?@bind-value=Name?@bind-value:event="oninput"/>@bind-value:event="oninput" 是指示 Blazor 使用即時更改檢測的關(guān)鍵。首先我們告訴 Blazor 我們要將輸入框的 value HTML 屬性綁定到我們的 Name 成員 (@bind-value=Name),然后我們告訴 Blazor hook 到 HTML 元素的 oninput 事件,這樣每次綁定都會立即發(fā)生元素的值發(fā)生變化(@bind-value:event="oninput")。
*** 指定自定義綁定格式 通過為 @bind 指令的格式屬性指定一個值來指定在用戶界面中使用的自定義格式。
將以下標(biāo)記添加到我們的頁面并運行應(yīng)用程序。
<label>Date?of?birth?=?@DateOfBirth?.ToString("MMMM?d,?yyyy")</label> <input?@bind-value=DateOfBirth?@bind-value:format="yyyy-MM-dd"/>當(dāng)應(yīng)用程序運行時,輸入 ISO 格式的日期(例如 1969-07-21)。雖然日期在 <label> 中顯示為 July 21, 1969,但 <input> 控件以我們在 @bind-value:format="yyyy-MM-dd" 中指定的 ISO 顯示它。
注意: 輸入的任何與指定格式不匹配的值都將被丟棄。因此,我們不能設(shè)置 @bind-value:event="oninput",因為 Blazor 會嘗試在每次按鍵時解析輸入,但輸入的值不可能在單次按鍵后有效,因此輸入值將干脆消失。這是我建議在編輯數(shù)據(jù)時在 EditForm[9] 中使用 Blazor <Input*> 組件的原因之一,因為這使我們能夠使用諸如 <InputDate> 之類的組件。
如 Descending from InputBase[10] 部分所述,Blazor 輸入組件具有一對互補的受保護(hù)方法,用于將綁定值轉(zhuǎn)換為字符串和從字符串轉(zhuǎn)換為字符串。
工作原理
@bind 指令不會添加代碼來直接綁定到我們的成員,而是簡單地將其轉(zhuǎn)換為字符串值/從字符串值轉(zhuǎn)換。相反,它通過 BindConverter 重定向當(dāng)前值的表示和輸入值的解析。
如果我們查看 Blazor 為單向綁定(例如 class=@OurCssClass)生成的 .cs 文件,我們會看到 C# 看起來像這樣(為簡潔起見進(jìn)行了編輯)。
protected?override?void?BuildRenderTree(RenderTreeBuilder?__builder) {_builder.AddAttribute(1,?"class",?OurCssClass); }現(xiàn)在,如果我們查看生成的雙向綁定文件,我們將看到類似于以下(有刪減)代碼的內(nèi)容,用于顯示該值:
protected?override?void?BuildRenderTree(RenderTreeBuilder?__builder) {_builder.AddAttribute(1,?"value",...BindConverter.FormatValue(Name));以及類似于以下(有刪減)代碼,用于將用戶輸入轉(zhuǎn)換回綁定成員。
__builder.AddAttribute(11,?"onchange",...EventCallback.Factory.CreateBinder(this,?__value?=>?Name?=?__value,?Name)); }代碼 hook 到 HTML onchange 事件,然后在事件觸發(fā)時設(shè)置我們的成員值。
設(shè)置 @bind-value:format 指令屬性值時的不同之處在于我們提供的格式在生成的代碼中傳遞給了 BindConverter.Format 和 EventCallback.Factory.CreateBinder。
...BindConverter.FormatValue(Name,?format:?"yyyy-MM-dd"); //?and CreateBinder(....,?format:?"yyyy-MM-dd");指定自定義 culture
世界上的人們有不同的習(xí)俗和文化,這是使世界變得如此有趣的原因之一。不幸的是,這也是使編寫軟件更加困難的原因之一。
將以下標(biāo)記添加到我們的頁面:
<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 土耳其里拉。當(dāng)然,居住在土耳其的人會知道要輸入 12,42,但這凸顯了當(dāng)我們的應(yīng)用程序打算在其他國家/地區(qū)使用時正確指定文化的必要性。
與 format 指令屬性一樣,指定的 @bind-value:culture 將作為命名(可選)值傳遞給 Binder 和 BindConverter。
如果您還沒有聽說過土耳其測試,那么我建議您閱讀這篇優(yōu)秀的文章[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]這篇優(yōu)秀的文章: http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html
總結(jié)
以上是生活随笔為你收集整理的Blazor University (7)组件 — 双向绑定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF 基础控件之 GroupBox样式
- 下一篇: AgileConfig 1.6.0 发布