当前位置: 首页 > news >正文

第三章 组件(5)- 数据绑定

双向绑定功能

一、 @bind

在Blazor项目中,实现数据的双向绑定很简单,只需要在组件的HTML输入元素使用@bind属性来绑定字段、属性或 Razor 表达式值即可。

  • 实际上,@bind 指令是 @bind-value 指令的缩写形式,它绑定的是元素的 value 属性。因此所有带有 value 属性的HTML 元素都可以使用 @bind 进行双向数据绑定
  • 当进行双向绑定后,HTML元素对绑定数据的修改会触发组件的渲染

在下面实例中,在Bind组件中分别在两个Input元素中通过@bind属性指令进行字段和属性的绑定。当Input元素失去焦点时,将更新其绑定的字段或属性。

  • 示例-Bind.razor

    @page "/bind"
    @rendermode InteractiveServer
    
    <PageTitle>Bind</PageTitle>
    
    <h1>Bind Example</h1>
    
    <p>
        <label>
            inputValue: 
            <input @bind="inputValue" />
        </label>
    </p>
    
    <p>
        <label>
            InputValue: 
            <input @bind="InputValue" />
        </label>
    </p>
    
    <ul>
        <li><code>inputValue</code>: @inputValue</li>
        <li><code>InputValue</code>: @InputValue</li>
    </ul>
    
    @code {
        private string? inputValue;
    
        private string? InputValue { get; set; }
    }
    

需要注意的是,在Blazor中,仅在渲染组件时才会更新UI来反应字段和属性的更新,而不会因为字段和属性的更新而立即做出响应。查看下面的例子,在组件初始化完成的三秒后,对InputValue属性进行了更改,但是可以发现更改后页面中第二个Input框的值并没有发生变化,只有在第一个Input框修改了内容并失焦后(引发渲染),才发生变化。

  • 示例-Bind.razor

    @page "/bind"
    @rendermode InteractiveServer
    
    <PageTitle>Bind</PageTitle>
    
    <h1>Bind Example</h1>
    
    <p>
        <label>
            inputValue:
            <input @bind="inputValue" />
        </label>
    </p>
    
    <p>
        <label>
            InputValue:
            <input @bind="InputValue" />
        </label>
    </p>
    
    <ul>
        <li><code>inputValue</code>: @inputValue</li>
        <li><code>InputValue</code>: @InputValue</li>
    </ul>
    
    @code {
        private string? inputValue;
    
        private string? InputValue { get; set; }
    
        protected override  void OnInitialized()
        {
            Task.Run(() =>
            {
                Thread.Sleep(3000);
                InputValue = "值被改变了";
            });
        }
    }
    

指定绑定事件

Input元素中,使用@bind时,默认的绑定事件是onchange事件,也就是在输入框失焦时,才会对绑定数据进行更新。如果希望在其他的DOM事件上绑定数据,可以通过@bind:event="{EVENT}"来指定({EVENT}占位符表示对应的事件)。

下面的示例中,通过@bind:event="oninput"将绑定时机修改到oninput事件,这样当输入框发生变化时候就会更新绑定数据,而不需要等到失焦时才更新。

  • 示例-BindEvent.razor

    @page "/bind-event"
    @rendermode InteractiveServer
    
    <PageTitle>BindEvent</PageTitle>
    
    <h1>BindEvent Example</h1>
    <p>
        <label>
            InputValue:
            <input @bind="InputValue" @bind:event="oninput" />
        </label>
    </p>
    
    <ul>
        <li><code>InputValue</code>: @InputValue</li>
    </ul>
    
    @code {
        public string? InputValue { get; set; }
    }
    

注意@bind:event必须与@bind同时存在,先有@bind,再有@bind:event

指定回调方法

如果在绑定数据更新后,需要执行回调方法,可以使用@bind:after来指定对应的方法。

  • 需要注意的是,@bind:after支持异步回调,但是不支持EventCallbackEventCallback<T>

  • 示例-BindAfter.razor

    @page "/bind-after"
    @rendermode InteractiveServer
    
    <PageTitle>BindAfter</PageTitle>
    
    <h1>BindAfter Example</h1>
    
    <input type="text" @bind="text" @bind:after="() => { }" />
    
    <input type="text" @bind="text" @bind:after="After" />
    
    <input type="text" @bind="text" @bind:after="AfterAsync" />
    
    <h2>Components</h2>
    
    <InputText @bind-Value="text" @bind-Value:after="() => { }" />
    
    <InputText @bind-Value="text" @bind-Value:after="After" />
    
    <InputText @bind-Value="text" @bind-Value:after="AfterAsync" />
    
    @code {
        private string text = "";
    
        private void After() {}
        private Task AfterAsync() { return Task.CompletedTask; }
    }
    

二、@bind:get与@bind:set

上文中,使用了@bind实现了数据的双向绑定,十分方便,但在实际的项目开发中很多时候会遇到需要将绑定数据的获取和设置分开进行。比如,希望在对绑定数据进行更新时,做一些逻辑处理。如果绑定的数据是一个属性,那还还好,可以通过set来做一定的处理。但是如果绑定的是一个字段呢?或者说绑定的是个属性,但是逻辑较为复杂,不适合放在set中的时候,就希望能将绑定数据的获取和设置分离开来。

Blazor为此提供了@bind:get@bind:set,来解决这一问题,但在学习这对属性指令之前,先来看看通过事件触发能不能实现这个需求。

事件触发实现读写分离

仔细观察下面的例子,Input元素使用@变量和事件处理方法的方式来实现绑定数据的读写分离。

  • 示例-ErrorBind.razor

    @page "/error-bind"
    @rendermode InteractiveServer
    
    <p>
        <input value="@inputValue" @oninput="OnInput" />
    </p>
    
    <p>
        <code>inputValue</code>: @inputValue
    </p>
    
    @code {
        private string? inputValue;
    
        private void OnInput(ChangeEventArgs args)
        {
            var newValue = args.Value?.ToString() ?? string.Empty;
    
            inputValue = newValue.Length > 4 ? "Long!" : newValue;
        }
    }
    

通过直接事件触发,来实现数据双向绑定的做法是有弊端的!

在上面的例子中,在提供第四个字符后,OnInput事件处理程序将 inputValue 的值更新为 Long!。 但是,用户可以继续在 UI 中向元素值添加字符。 inputValue的值并没有随着每次击键而绑定回元素的值,只能进行单向数据绑定。原因是Blazor 不知道代码打算在事件处理程序中修改 inputValue 的值。 Blazor 不会尝试强制 DOM 元素值和 .NET 变量值进行匹配,除非它们通过 @bind 语法绑定。

  • 注意,实际上即使例子中换成了@bind和事件触发的方式也是存在问题的,如果直接绑定属性,并且在set中做处理就不会。

@bind:get@bind:set

为了应对绑定数据读写分离上的问题,Blazor提供了一对属性指令,即@bind:get@bind:set

  • @bind:get:指定要绑定的字段、属性。

  • @bind:set:指定给字段/属性设置值的回调,绑定的是 C#方法,该方法符合Func<T,Task>Action<T>委托的声明要求。默认情况下会使用onchange事件调用@bind:set绑定的方法,也就是输入框失去焦点后执行。

  • @bind:get@bind:set 修饰符始终一起使用。

  • 示例-RightBind.razor

    @page "/right-bind"
    @rendermode InteractiveServer
    
    <p>
        <input @bind:get="inputValue" @bind:set="OnInputAsync" @bind:event="oninput" />
    </p>
    
    <p>
        <code>inputValue</code>: @inputValue
    </p>
    
    @code {
        private string? inputValue;
    
        private Task OnInputAsync(string? value)
        {
            var newValue = value ?? string.Empty;
    
            inputValue = newValue.Length > 4 ? "Long!" : newValue;
    
            return Task.CompletedTask;
        }
    }
    

一般情况下,如果逻辑较为简单,那么通过@bind直接绑定到属性,在属性的set上去做处理同样可以解决问题。

三、使用 <select> 元素的多个选项选择

@bind支持使用 <select> 元素的 multiple 多项选择。

下面例子中,分别使用事件和@bind实现了类似的效果,可以进行对比参考。

  • BindMultipleInput.razor

    @page "/bind-multiple-input"
    @rendermode InteractiveServer
    
    <h1>Bind Multiple <code>input</code>Example</h1>
    
    <p>
        <label>
            Select one or more cars:
            <select @onchange="SelectedCarsChanged" multiple>
                <option value="audi">Audi</option>
                <option value="jeep">Jeep</option>
                <option value="opel">Opel</option>
                <option value="saab">Saab</option>
                <option value="volvo">Volvo</option>
            </select>
        </label>
    </p>
    
    <p>
        Selected Cars: @string.Join(", ", SelectedCars)
    </p>
    
    <p>
        <label>
            Select one or more cities:
            <select @bind="SelectedCities" multiple>
                <option value="bal">Baltimore</option>
                <option value="la">Los Angeles</option>
                <option value="pdx">Portland</option>
                <option value="sf">San Francisco</option>
                <option value="sea">Seattle</option>
            </select>
        </label>
    </p>
    
    <span>
        Selected Cities: @string.Join(", ", SelectedCities)
    </span>
    
    @code {
        public string[] SelectedCars { get; set; } = new string[] { };
        public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
    
        private void SelectedCarsChanged(ChangeEventArgs e)
        {
            if (e.Value is not null)
            {
                SelectedCars = (string[])e.Value;
            }
        }
    }
    

<select> 元素选项绑定到 C# 对象 null

由于以下原因,没有将 <select> 元素选项值表示为 C# 对象 null 值的合理方法:

  • HTML 属性不能具有 null 值。 HTML 中最接近的 null 等效项是 <option> 元素中缺少 HTML value 属性。
  • 选择没有 value 属性的 <option> 时,浏览器会将该 <option> 的元素的文本内容视为value值。

HTML 中最合理的 null 等效项是空字符串value。 Blazor 框架处理 null 到空字符串之间的转换,以便双向绑定到 <select> 的值。

数据处理

字符串格式化

可以使用@bind:format="{FORMAT STRING}"对绑定数据进行格式化。

  • 目前Blazor中暂时只支持对日期的字符串格式化,对于其他格式表达式(如货币或数字格式)暂时不可用。

  • DataBinding.razor

    @page "/date-binding"
    
    <PageTitle>Date Binding</PageTitle>
    
    <h1>Date Binding Example</h1>
    
    <p>
        <label>
            <code>yyyy-MM-dd</code> format:
            <input @bind="startDate" @bind:format="yyyy-MM-dd" />
        </label>
    </p>
    
    <p>
        <code>startDate</code>: @startDate
    </p>
    
    @code {
        private DateTime startDate = new(2020, 1, 1);
    }
    

无法分析的值

如果用户向数据绑定元素提供无法分析的值,则在触发绑定事件时,无法分析的值会自动还原为以前的值。例如绑定的数据类型为int,但是输入了100.22,如果本来的值为100,则会在失焦(onchange)时还原为100或在更改时(oninput)保留100。

处理方案

无论是格式化还是遇到无法分析的值,实际上都可以通过其他方式解决,比如使用 @bind:get/@bind:set 修饰符(如本文前面所述)或使用自定义 getset 访问器逻辑绑定到属性来处理无效输入。或者将输入组件(例如 InputNumber<TValue>InputDate<TValue>)与窗体验证配合使用。

组件参数的双向绑定

一、单向绑定

默认情况下,组件参数的传递是单向的,这个单向的意思是,当父组件中使用了某个子组件,并给子组件设置了某个参数值,会出现如下情况:

  • 如果父组件发生了渲染并且给子组件设置的参数值发生了变化,值就会传递到子组件并引发子组件的渲染。
  • 如果是子组件对自身的组件参数发生的修改,即使子组件渲染后自身更新了对应的数据,但对父组件而言,是没有任何影响的。

如下面示例中,点击子组件的按钮,只能修改自身的展示数据。

  • 子组件-ParamTestChild.razor

    <div>
        <label>子控件:</label>
        <input type="text" value="@Data" />
        <button @onclick="SetParam">按钮</button>
    </div>
    @code {
        [Parameter]
        public int Data { get; set; }
    
        private void SetParam(MouseEventArgs e)
        {
            Data = 300;
        }
    }
    
  • 父组件-ParamTestFather.razor

    @page "/param-test"
    @rendermode InteractiveServer
    @using BlazorAppServer.Components.Common
    
    <h3>ParamTestFather</h3>
    <ParamTestChild Data="@fatherData" />
    
    <label>父控件:</label>
    <input type="text" value="@fatherData" />
    <button @onclick="SetParam">按钮</button>
    
    @code {
        private int fatherData { get; set; }
    
        private void SetParam(MouseEventArgs e)
        {
            fatherData = 100;
        }
    }
    

在这里插入图片描述

二、双向绑定

想要对组件参数进行双向数据绑定,常见方案是将子组件中的属性绑定到其父组件中的属性。 此方案称为链接绑定,因为多个级别的绑定会同时进行。

具体做法如下

在子组件中定义组件参数,提供给父组件进行数据绑定

[Parameter]
public int Year { get; set; }

在子组件中定义EventCallback/EventCallback<T>事件处理方法,以支持从子组件更新父组件中的属性。

  • 其组件参数名称,建议按照约定进行命名,其命名约定为{PARAMETERNAME}Changed
[Parameter]
public EventCallback<int> YearChanged { get; set; }

private async Task UpdateYearFromChild()
{
    await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}

在父组件中使用@bind-{PropertyName}对组件参数进行数据绑定,@bind-event:{EventCallBackName}指定对应的事件处理程序。

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
@* 如果事件处理方法的命名按照约定来,那么可以简写如下 *@
<ChildBind @bind-Year="year"/>

完整示例

  • ChildBind.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 {
        [Parameter]
        public int Year { get; set; }
    
        [Parameter]
        public EventCallback<int> YearChanged { get; set; }
    
        private async Task UpdateYearFromChild()
        {
            await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
        }
    }
    
  • Parent.razor

    @page "/parent"
    @rendermode InteractiveServer
    
    <PageTitle>Parent</PageTitle>
    
    <h1>Parent Example</h1>
    
    <p>Parent <code>year</code>: @year</p>
    
    <button @onclick="UpdateYear">Update Parent <code>year</code></button>
    
    <ChildBind @bind-Year="year"/>
    
    @code {
        private int year = 1979;
    
        private void UpdateYear()
        {
            year = Random.Shared.Next(1950, 2021);
        }
    }
    

链接绑定多个组件的组件参数

链接绑定多个组件的组件参数,其实就是组件参数双向绑定的嵌套用法。

可以将参数绑定到任意数量的嵌套组件,但必须采用单向数据流:

  • 更改通知沿层次结构向上传递。
  • 新参数值按层次结构向下传递。

需要注意的是,在实现过程中,定义的EventCallback/EventCallback<T>事件处理方法,其名字必须根据约定来,否则会报错。

仔细查看下面的例子,其中最需要注意的是NestedChild.razor组件:

  • 使用 @bind:get 语法将ChildMessage的值分配给GrandchildMessage

  • 使用 @bind:set 绑定ChildMessageChanged事件处理程序,当NestedGrandchild组件中,对GrandchildMessage发生变化时,执行ChildMessageChanged事件处理程序,从而完成对父组件的绑定数据的更新。

  • 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}");
        }
    }
    
  • 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:get="ChildMessage" 
            @bind-GrandchildMessage:set="ChildMessageChanged" />
    </div>
    
    @code {
        [Parameter]
        public string? ChildMessage { get; set; }
    
        [Parameter]
        public EventCallback<string?> ChildMessageChanged { get; set; }
    
        private async Task ChangeValue()
        {
            await ChildMessageChanged.InvokeAsync(
                $"Set in Child {DateTime.Now}");
        }
    }
    
  • Parent.razor

    @page "/parent"
    @rendermode InteractiveServer
    
    <PageTitle>Parent 2</PageTitle>
    
    <h1>Parent Example 2</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}";
        }
    }
    
http://www.dtcms.com/a/26696.html

相关文章:

  • 深研究:与Dify建立研究自动化应用
  • 撕碎QT面具(2):groupBox内容居中显示
  • MySQL 一条 SQL 执行流程解析
  • /etc/docker/daemon.json这个跟/etc/yum.repos.d/docker-ce.repo这个文件的关系
  • DeepSeek 助力 Vue 开发:打造丝滑的右键菜单(RightClickMenu)
  • 分布式大语言模型服务引擎vLLM论文解读
  • tortoiseSVN 如何克隆项目到本地
  • WEB项目接入Deepseek(硅基流动)
  • Shapr3D在ipad上无法识别鼠标点击问题
  • NPB安装使用教程
  • 基于iptables的Docker端口白名单控制
  • C++ 程序 return value 1 是什么原因
  • 寒假阶段学习总结
  • C++:pthread的使用
  • SpringSecurity基于配置方法控制访问权限:MVC匹配器、Ant匹配器
  • hive 编译慢问题处理
  • FontConfig封装分享
  • Token Embedding(词嵌入)和Positional Encoding(位置编码)的矩阵形状关系及转换过程
  • [grub]修改启动项选项来区分不同系统
  • fastapi sqlalchemy 日志 logging 写入异常 多进程文件写入异常
  • python-leetcode 37.翻转二叉树
  • Javascript网页设计实例:通过JS实现上传Markdown转化为脑图并下载脑图
  • 火语言RPA--Excel关闭保存文档
  • 【HarmonyOS Next】鸿蒙监听手机按键
  • 汇能感知的光谱相机/模块产品有哪些?
  • 【python】tkinter简要教程
  • oppo,汤臣倍健,康冠科技,高途教育25届春招内推
  • 记录一下windows11编译Openpose的过程
  • 使用VSCODE开发C语言程序
  • 【PLL】应用:时钟生成