【Blazor】|?總結/Edison Zhou
大家好,我是Edison。
最近在學習Blazor做全棧開發,因此根據老習慣,我會將我的學習過程記錄下來,一來體系化整理,二來作為筆記供將來翻看。
本篇,我們來了解下在Blazor中數據是如何綁定的。
關于數據綁定如果希望 HTML 元素顯示值,可以編寫代碼來更改顯示內容。如果值發生更改,則需要編寫額外的代碼以更新顯示內容。
在 Blazor 中,可以使用數據綁定將 HTML 元素連接到字段、屬性或表達式。
這樣,當值發生更改時,HTML 元素便會自動更新。更新通常在更改后迅速發生,并且我們無需編寫任何更新代碼。
例如,我們使用@bind指令完成當變量被更改時,h1和input標簽的值也同步更新:
@page "/"<h1>My favorite pizza is: @favPizza</h1><p>Enter your favorite pizza:<input @bind="favPizza" />
</p>@code {private string favPizza { get; set; } = "Margherita"
}
@bind指令比較智能,它大概可以知道你需要綁定標簽的哪個屬性,例如:將其綁定到input標簽時,它會綁定value屬性。而將其綁定到checkbox中,它則自動綁定checked屬性。
將元素綁定到特定事件
默認情況下,@bind指令對于input控件通常會綁定到DOM onchange事件。對于上面的例子來說,當在文本框中輸入了數據時,只有當離開文本框或選擇按下Enter鍵或者Tab鍵,才會觸發DOM onchange事件讓h1標簽的內容發生改變。
假設,我們希望在文本框中輸入任何內容時,都會觸發h1標簽內容的更改。這個事件就不再是DOM onchange事件了而是DOM oninput事件,因此,我們可以借助 @bind-value 和 @bind-value:event 指令來綁定到oninput事件:
@page "/"<h1>My favorite pizza is: @favPizza</h1><p>Enter your favorite pizza:<input @bind-value="favPizza" @bind-value:event="oninput" />
</p>@code {private string favPizza { get; set; } = "Margherita"
}
實現效果:
設置綁定值的格式
在很多場景中,我們可能需要對日期進行本地化的格式轉換。這里,我們就可以借助@bind:format指令來指定格式:
@page "/ukbirthdaypizza"<h1>Order a pizza for your birthday!</h1><p>Enter your birth date:<input @bind="birthdate" @bind:format="dd-MM-yyyy" />
</p>@code {private DateTime birthdate { get; set; } = new(2000, 1, 1);
}
此外,我們也可以采用屬性的get/set訪問器來實現自定義的格式轉換,例如下面的示例:
@page "/pizzaapproval"
@using System.Globalization<h1>Pizza: @PizzaName</h1><p>Approval rating: @approvalRating</p><p><label>Set a new approval rating:<input @bind="ApprovalRating" /></label>
</p>@code {private decimal approvalRating = 1.0;private NumberStyles style = NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");private string ApprovalRating{get => approvalRating.ToString("0.000", culture);set{if (Decimal.TryParse(value, style, culture, out var number)){approvalRating = Math.Round(number, 3);}}}
}
組件參數的綁定(雙向綁定)
在有些場景中,父組件中嵌套了子組件,我們希望父組件中的變化能夠同步更新到子組件,同理,子組件中的變化能夠同步更新父組件中。實現的方式就是通過組件參數(Parameter),而這個場景也被稱之為鏈式綁定(Chained Bind)。
在Blazor中,我們可以通過 @bind-{PROPERTY} 指令來實現鏈式綁定,其中的 {PROPERTY} 占位符表示要綁定的屬性名字。
例如,我們有以下兩個組件,Parent-1.razor是父組件,其中嵌套了 ChildBind.razor 這個子組件。
ChindBind.razor:
<div class="card bg-light mt-3" style="width:18rem "><div class="card-body"><h3 class="card-title">ChildBind Component</h3><p class="card-text">Child <code>Year</code>: @Year</p><button @onclick="UpdateYearFromChild">Update Year from Child</button></div>
</div>@code {private Random r = new();[Parameter]public int Year { get; set; }[Parameter]public EventCallback<int> YearChanged { get; set; }private async Task UpdateYearFromChild(){await YearChanged.InvokeAsync(r.Next(1950, 2021));}
}
Parent-1.razor:
@page "/parent-1"<h1>Parent Component</h1><p>Parent <code>year</code>: @year</p><button @onclick="UpdateYear">Update Parent <code>year</code></button><ChildBind @bind-Year="year" />@code {private Random r = new();private int year = 1979;private void UpdateYear(){year = r.Next(1950, 2021);}
}
可以看到,這里Parent-1.razor中通過@bind-Year指令與子組件的Year屬性進行了綁定。
需要注意的是,通常情況下,我們還需要設置一個@bing-Year:event指令,不過由于我們定義的事件回調的名字YearChanged是符合自動匹配的,即命名格式是 {PARAMETER NAME}Changed,也就可以省略@bind-Year:event="YearChanged"這個設置,這就是所謂的“約定大于配置”。因此,它其實等價于:
<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
因此,我們可以知道,只需要在HTML屬性中加上@bind-{PROPERTY}指令,就是告訴Blazor不僅要將更改到推送到組件,還要觀察組件的任何修改并及時更新自己的狀態。通常來說,這種在父組件和子組件之間的數據綁定 也叫做 雙向綁定。
同時,我們也注意到在Blazor中事件回調(委托)的統一類型為:EventCallback。我們在子組件中使用的是InvokeAsync()方法也說明它是線程安全的。
實現效果:
在一個更真實常見的場景中,我們可能希望實現數據實施修改的聯動更新,類似于下面的例子。
PasswordEntry.razor:
<div class="card bg-light mt-3" style="width:22rem "><div class="card-body"><h3 class="card-title">Password Component</h3><p class="card-text"><label>Password:<input @oninput="OnPasswordChanged"requiredtype="@(showPassword ? "text" : "password")"value="@password" /></label><span class="text-danger">@validationMessage</span></p><button class="btn btn-primary" @onclick="ToggleShowPassword">Show password</button></div>
</div>@code {private bool showPassword;private string? password;private string? validationMessage;[Parameter]public string? Password { get; set; }[Parameter]public EventCallback<string> PasswordChanged { get; set; }private Task OnPasswordChanged(ChangeEventArgs e){password = e?.Value?.ToString();if (password != null && password.Contains(' ')){validationMessage = "Spaces not allowed!";return Task.CompletedTask;}else{validationMessage = string.Empty;return PasswordChanged.InvokeAsync(password);}}private void ToggleShowPassword(){showPassword = !showPassword;}
}
PasswordBinding.razor:
@page "/password-binding"<h1>Password Binding</h1><PasswordEntry @bind-Password="password" /><p><code>password</code>: @password
</p>@code {private string password = "Not set";
}
最終效果:
組件參數綁定的最佳實踐
我們可以在多層嵌套的組建中綁定組件參數,但是我們必須遵循這類單向數據綁定的流程:
更改通知是逐級向上流動
新的參數值是逐級向下流動
一個推薦的方式是只在父組件中存儲源數據,以此避免在狀態需要更新時容易產生的混淆。
例如,下面這個例子:
Parent2.razor:
@page "/parent-2"<h1>Parent Component</h1><p>Parent Message: <b>@parentMessage</b></p><p><button @onclick="ChangeValue">Change from Parent</button>
</p><NestedChild @bind-ChildMessage="parentMessage" />@code {private string parentMessage = "Initial value set in Parent";private void ChangeValue(){parentMessage = $"Set in Parent {DateTime.Now}";}
}
NestedChild.razor:
<div class="border rounded m-1 p-1"><h2>Child Component</h2><p>Child Message: <b>@ChildMessage</b></p><p><button @onclick="ChangeValue">Change from Child</button></p><NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>@code {[Parameter]public string? ChildMessage { get; set; }[Parameter]public EventCallback<string> ChildMessageChanged { get; set; }private string BoundValue{get => ChildMessage ?? string.Empty;set => ChildMessageChanged.InvokeAsync(value);}private async Task ChangeValue(){await ChildMessageChanged.InvokeAsync($"Set in Child {DateTime.Now}");}
}
NestedGrandchild.razor:
<div class="border rounded m-1 p-1"><h3>Grandchild Component</h3><p>Grandchild Message: <b>@GrandchildMessage</b></p><p><button @onclick="ChangeValue">Change from Grandchild</button></p>
</div>@code {[Parameter]public string? GrandchildMessage { get; set; }[Parameter]public EventCallback<string> GrandchildMessageChanged { get; set; }private async Task ChangeValue(){await GrandchildMessageChanged.InvokeAsync($"Set in Grandchild {DateTime.Now}");}
}
從示例中可以看出,它遵循了兩個原則:
(1)源數據是自頂向下流動,即parentMessage 和 BoundValue 兩個值。
(2)事件通知是自底向上流動,即子組件的ChangeValue方法都會調用EventCallback來向上通知。
最終效果:
小結
本篇,我們了解了數據如何在Blazor中進行數據的綁定。
下一篇,我們學習一下在Blazor中數據綁定的各種花樣。
參考資料
Microsoft Docs,《與Blazor Web應用中的數據交互》
Microsoft Docs,《Blazor數據綁定》
年終總結:Edison的2021年終總結
數字化轉型:我在傳統企業做數字化轉型
C#刷題:C#刷劍指Offer算法題系列文章目錄
.NET面試:.NET開發面試知識體系
.NET大會:2020年中國.NET開發者大會PDF資料