Blazor学习之旅数据绑定

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Blazor学习之旅数据绑定相关的知识,希望对你有一定的参考价值。

【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 NAMEChanged,也就可以省略@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"
                       required
                       type="@(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资料

以上是关于Blazor学习之旅数据绑定的主要内容,如果未能解决你的问题,请参考以下文章

Blazor学习之旅数据共享

Blazor 修仙之旅 - Ant Design of Blazor

Blazor 修仙之旅 - Ant Design of Blazor

Blazor 修仙之旅 - Ant Design of Blazor

Blazor 修仙之旅 - Ant Design of Blazor

Blazor学习之旅初步了解Blazor