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

Components

续上篇。

Code generated HTML attributes

无法在<>内添加C#的控制语句。

像下面这种也不行:

<div @IfYouCanSeeThisTextThenTheCodeWasNotExecutedHere />

Razor只在下面的几处执行C#代码:

  • 在元素的内容区域,如<span>@GetSomeHtml()</span>
  • 在元素的属性赋值区域,如<img src=@GetTheImageForTheUrl() />
  • @code块部

若要编码生成属性键值对,需要采用一种叫做Attribute splattin的方式,也就是将一个Dictionary<string, object>现例赋值给元素的@attributes

<div @attributes=MyCodeGeneratedAttributes/>

@code
{
  Dictionary<string, object> MyCodeGeneratedAttributes;

  protected override void OnInitialized()
  {
    MyCodeGeneratedAttributes = new Dictionary<string, object>();
    for(int index = 1; index <= 5; index++)
    {
      MyCodeGeneratedAttributes["attribute_" + index] = index;
    }
  }
}

上述代码的输出结果为:

<div attribute_1="1" attribute_2="2" attribute_3="3" attribute_4="4" attribute_5="5"></div>

对于HTML元素中那些默认不带有值的属性,只要其有值就会被激活。下面的例子中readonly和disabled的值为false,但是readonly和disabled会被激活:

<input readonly="false" disbabled="false"/>

在Blazor中,如果:

<input readonly=@true disabled=@false/>

则上面的disabled不会出现在结果中。

Capturing unexpected parameters

接收键值对参数的方式跟attribute splatting类似,都涉及一个Dictionary<string, object>现例,以及@attributes

<div class="row" role="img" aria-label=@alt>
  <img src=@src @attributes=AllOtherAttributes />
</div>

@code 
{
  [Parameter]
  public string src { get; set; }

  [Parameter]
  public string alt { get; set; }

  [Parameter(CaptureUnmatchedValues = true)]
  public Dictionary<string, object> AllOtherAttributes { get; set; }
}

Replacing attributes on child components

可以指定参数合集的默认值:

<div first="1" second="2" third="3" fourth="4"  @attributes=AllOtherAttributes>
  Right-click and inspect the HTML for this element to see the results!
</div>

若传入的是:

<ChildComponent first="consumer-value-1" second="consumer-value-2" />

则会生成:

<div first="consumer-value-1" second="consumer-value-2" third="3" fourth="4">
  Right-click and inspect the HTML for this element to see the results!
</div>

如果把默认值指定在参数合集之后,则能避免其被修改。下面的例子中,为了避免type="number"被修改,但是允许class="form-control"被修改,需要这么写:

<input class="form-control"  @attributes=AllOtherAttributes type="number"/>

简单地说,参数合集是Inserted,在其之前是Replacable的参数,在其之后是Protected参数,合称 R.I.P.。

Component lifecycles

Blazor组件基类提供了许多虚函数,供派生类覆改。

Parent renders ----------> Create instance if required
                 |
                 + ------> SetParametersAsync
                                    |
                                    v
                            OnInitialized / OnInitializedAsync [x]
                                    |
                                    v
                            OnParameterSet / OnParamterSetAsync
                                    |
                                    +--------> StateHasChanged <----- Events
                                                    |
                                                    v
                                                ShouldRender [x]
                                                    | true
                                                    v
                                                BuildRenderTree ----+
                                                                    |
                                                                    v
                                                                Create added child components
                                                                    |
                                                                    v
                                                                Dispose removed child components 
                                                                    |
                            OnAfterRender / OnAfterRenderAsync  <---+
Parent removed from render tree ------> IDisposable Dispose -------> Dispose child components
  • SetParametersAsync, 只要上级节点渲染,此方法必备调用
    • 入参会被装置在一个ParameterView中。
    • 这是个发起与服务端异步沟通的好时机。
    • 在覆改中调用base.SetParametersAsync会设置[Parameter]辖属
    • 也是设置默认参数值的时机。
  • OnInitialized / OnInitializedAsync
    • ParameterCollection初始化之后,这些个方法会被执行。
    • 和SetParametersAsync不同之处在于:此时的组件状态已经初始化了。
    • 但是,此方法只在组件首次创建的时候初始化一次。
    • 如果是@page,并且浏览去往的是同一URL,那么Blazor不会嗲用IDisposable.Dispose,也不会执行OnInitialized
  • OnParametersSet / OnParametersSetAsync
    • 首次例现化的时候,会跟在OnInitializedAsync之后执行。
    • 后续会跟在SetParametersAsync之后执行
  • StateHasChanged
    • 告知Blazor需要刷新了
    • 在异步方法中告知Blazor进行半途刷新
  • ShouldRender
    • 如若返回false,会阻止重新计算组件的RenderTree,减少非必要的刷新
    • 首次例现化的时候不会执行
  • BuildRenderTree
    • 在内存中构建一颗渲染树
    • 为了加快渲染速度,应该在循环中尽可能使用key指示
  • OnAfterRender / OnAfterRenderAsync
    • 每次组件刷新后,都会执行
    • 刷新的触发时间
      • 上级组件刷新
      • 用户交互事件
      • StateChanged被调用
    • 会传入一个参数firstRender告知是否是首次渲染
    • 只有在OnAfterRender执行后,访问其他组件的@ref才是安全的
    • 只有在firstRender为真的OnAfterRender执行后,访问其他元素的@ref才是安全的
  • Dispose
    • 不是ComponentBase的生命周期方法,但是是.NET对象的
    • 调用时机,组件从上级的渲染树中移除,且组件实现了IDisposable(@implements IDisposable),具体的方法是void IDisposable.Dispose()

值得注意的是,Blazor并不会等耗时的异步操作完成(例如从远端获取数据),而是尽可能的时候触发渲染。

  • SetParametersAsync
    • await前的动作
      • 继续处理生命周期(调用OnInitialized*或者OnParameterSet*
    • 退出时的动作
      • 无后续动作
    • base.SetParametersAsync必须在await之前执行,否则会触发InvalidOperationException异常
  • OnInitializedAsync
    • await前的动作
      • 渲染此组件
    • 退出时的动作
      • 继续处理生命周期
  • OnParametersSetAsync
    • await前的动作
      • 渲染此组件
    • 退出时的动作
      • 继续处理生命周期
  • OnAfterRenderAsync
    • await前的动作
      • 无前序动作
    • 退出时的动作
      • 无后续动作

简单地说,SetParametersAsync是唯一的不会阻断命周进程的异步操作。

OnAfterRenderAsync看似没有做额外工作,但它本身是执行链条的终点。如果需要在其await前触发渲染,需要写代码显示调用StateHasChanged,否则在await进行OnAfterRenderAsync会导致无限循环。

异步await的命周图:

Parent renders ---------------------------------> SetParametersAsync
        OnInitializedAsync  <---------------------- await Task1
    +------ await Task3                             await Task2
    |       await Task4                             Other code
    |       Other code
    |            |
    |            |
    |            |
    |            v
    |       OnParametersSetAsync
    |         await Task5
    |         await Task6
    |         Other code
    |            |
    |            |
    |            |
    |            v
    +-----> StateHasChanged
                 |
                 |
                 v
            ShouldRender
            true |
                 |
                 v
            BuildRenderTree -------> Create added child components
                                                |
                                                v
        OnAfterRenderAsync <------- Dispose removed child components
          await Task7
          await Task8
          Other code

只有首次await,Blazor才会刷新渲染,后续的await需要手动添加StateHasChanged才能触发渲染:

protected override async Task OnParametersSetAsync()
{
  // Automatically renders when next line starts to await
  await Task.Delay(1000); 

  // Explicitly render when next line starts to await
  StateHasChanged();
  await Task.Delay(1000); 

  // Explicitly render when next line starts to await
  StateHasChanged();
  await Task.Delay(1000); 
}

(本篇完)