Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

06 Aug 2021

Blazor University学习笔记【三】

https://blazor-university.com/学习笔记。

Components

续上篇。

Two-way binding

默认情况下[Parameter]只是单向绑定,也就是从上级组件传递到下级组件,反之则不行。

若要双向绑定,调用组件的语法必须从:

<MyFirstComponent CurrentCounterValue=@currentCount/>

调整成

<MyFirstComponent @bind-CurrentCounterValue=currentCount/>

然后就会产生一个错误说:

InvalidOperationException: Object of type 'BlazorApp1.Components.MyFirstComponent' does not have a property matching the name 'CurrentCounterValueChanged'.

也就是说,双向绑定需要下级组件提供一个额外的回调来接收变更通告。

转译后的CS代码展示如下:

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();

Binding directives

假使有下列组件成员:

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

然后将其用于input元素:

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

可以观察到,在input元素输入文本的时候,只有点击回车或者焦点移走时,值才会更新到label元素上。

为了解决这个问题,需要将input上的绑定稍微修改下:

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

x可以为onchagne(默认的)或者是oninput。onchange的效果就是前面观察到的,但是此案例中,我们需要的是oninput的效果。

所以完整的写法是:

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

指示的属性还可以用于指示所绑定的内容的格式:

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

如果指定了格式,那么Blazor就会对输入进行解析,只有解析正确的情况才会接收其值,否则会清空输入框。上述属性不能与@bind-value:event="oninput",会导致每敲一个字符就解析一次且清空一次,无法输入有效的值。

推荐使用Blazor自带的<Input*>,比如说<InputDate>,而不是直接使用<input />

@bind-value:format的工作原理是添加一个BindConverter来处理转化关系。

@bind还支持culture,用于处理本地化:

<label>Bank balance = @BankBalance</label>
<input @bind-value=BankBalance @bind-value:culture=Turkish/>
private CultureInfo Turkish = CultureInfo.GetCultureInfo("tr-TR");

Cascading values

随着情况变复杂,有些组件只是为了居中传递参数。

文中举了一个跟招聘有关的应用:

  • 一个空缺表示为一个Vacancy,并对应一个Address
  • 一个意向表示为一个Application,一个Application对应一个Vacancy,以及对应一个意向者
  • 一个地址表示为一个Address,一个意向者关联有一个地址

操作:

  • ViewApplication.razor用于查看一个空缺,并显示对应的意向合集,以及对应的意向者信息
  • ViewApplication.razor由以下几个组件构成
    • ViewVacancy.razor
      • ViewAddress.razor
    • VewApplication.razor
      • ViewCandidate.razor
        • ViewAddress.razor

首要布局MainLayout.razor带有一个选项,可以隐匿意向者的名字和地址。若这个选项置上,则ViewCandidate.razor不应显示名字,ViewAddress.razor不应显示地址。

如何把隐匿选项的值从ViewApplication.razor传递给ViewCandidate.razor以及ViewAddress.razor是 Cascading values需要解决的问题。

Cascading values by name

可以使用CascadingValue组件来创建值的层叠递进:

@page "/"

<h1>Toggle the options</h1>
<input @bind-value=FirstOptionValue type="checkbox" /> First option
<br />
<input @bind-value=SecondOptionValue type="checkbox" /> Second option
<br />

<CascadingValue Name="FirstOption" Value=@FirstOptionValue>
  <CascadingValue Name="SecondOption" Value=@SecondOptionValue>
    <FirstLevelComponent />
  </CascadingValue>
</CascadingValue>

@code {
  bool FirstOptionValue;
  bool SecondOptionValue;
}

若不在CascadingValue指定名字,则需要在对应的辖属上指定:

@page "/"

<h1>Toggle the options</h1>
<input @bind-value=FirstOptionValue type="checkbox" /> First option
<br />
<input @bind-value=SecondOptionValue type="checkbox" /> Second option
<br />

<CascadingValue Name="FirstOption" Value=@FirstOptionValue>
  <CascadingValue Name="SecondOption" Value=@SecondOptionValue>
    <FirstLevelComponent />
  </CascadingValue>
</CascadingValue>

@code {
  [CascadingParameter(Name="FirstOption")]
  private bool FirstOption { get; set; }
  [CascadingParameter(Name="SecondOption")]
  private bool SecondOption { get; set; }
}

只要将辖属赋予属性CascadingParameter,即可变为递进值,在所有下级组件可访问。作为递进量值的辖属,可以任意命名。递进量值的名字由CascadingParameter给出。另外递进两者的访问访问一般设置成private。

有点全局变量的感觉。

Cascading values by type

如果不在CascadingParameter上设置Name,那么Blazor会以如下方式来查找这个辖属:

  • 辖属标记有CascadingPropertyAttribute属性
  • [CascadingProperty]未有指定Name
  • 辖属的类型与CascadingValue的类型一致
  • 辖属有设值器
  • 辖属是public的

下面这个CascadingValue:

<CascadingValue Value=@true>
  <SomeComponent/>
</CascadingValue>

可以匹配下面两个辖属:

Property1 = @Property1
Property2 = @Property2

@code
{
  [CascadingParameter]
  private bool Property1 { get; set; }

  [CascadingParameter]
  private bool Property2 { get; set; }
}

对于简单类型的层递值,可能需要额外的Name来区分它们;但是对于复合类型,可能其类型就足够定位相关的辖属,无须再指定Name。

假使有以下userPreference类型:

public class UserPreferences
{
  public bool ViewAnonymizedData { get; set; }
  public string DateFormat { get; set; }
  public string LanguageCode { get; set; }
}

相应的CascadingValue如下:

<CascadingValue Value=@UserPreferences>
</CascadingValue>

下级组件则需要以下代码:

@if (!UserPreferences.ViewAnonymizedData)
{
  <div>
    <span>Name</span> @Candidate.Name
  </div>
  <div>
    <span>Date of birth</span> @Candidate.DateOfBirth.ToString(UserPreferences.DateFormat)
  </div>
  <ViewAddress Address=@Candidate.Address/>
}
else
{
  <span>[Anonmymized view]</span>
}

@code
{
  [CascadingParameter]
  private UserPreferences UserPreferences { get; set; }
}

Overriding cascaded values

以下ViewSomeValue组件显示一个名为ValueToOverride的层递值:

<div>Values are @SomeValue1 / @SomeValue2</div>

@code
{
  [CascadingParameter(Name = "CascadedValue")]
  private string SomeValue1 { get; set; }

  [CascadingParameter(Name = "ValueToOverride")]
  private string SomeValue2 { get; set; }
}

以上组件在以下页面中消费:

@page "/overridden"

<CascadingValue Name="CascadedValue" Value=@CascadedValue>
  <CascadingValue Name="ValueToOverride" Value=@OuterValue>

    <h2>First level</h2>
    <ViewSomeValue />

    <CascadingValue Name="ValueToOverride" Value=@InnerValue>
      <h2>Second level</h2>
      <ViewSomeValue />
    </CascadingValue>

    <h2>Back to first level</h2>
    <ViewSomeValue />

  </CascadingValue>
</CascadingValue>

@code
{
  string CascadedValue = "CascadedValue";
  string OuterValue = "Outer value";
  string InnerValue = "Inner value";
}

以上描述了层叠递进值的作用域,倒数第二个<ViewSomeValue />获得的ValueToOverride是Inner value,倒数第一个获得的是Outer vvalue

(本篇完)

Categories

Tags