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

JavaScript interop

WebAssembly不支持以下API:

  • Media Capture
  • Popups
  • Web GL
  • Web Storage

必须通过JS访问以上API。

JS互操作有一些缺陷:

  • 不要在预渲染阶段进行
  • 不要过早使用ElementReference
  • 要废弃资源以避免资源泄漏
  • 要避免在废弃的.NET引用上调用方法
  • 不要在Blazor还没有初始化的时候调用.NET方法

JavaScript boot process

Blazor的初始化一定是在浏览器加载完资源之后。

所以推荐的做法是由Blazor发起到JS的互操作。

若使用ServerPrerendered,可以看到:

Blazor initialised: 42:22.559
JavaScript initialised: 42:22.631
Blazor initialised: 42:22.690

也就是Blazor初始化了两次。

Calling JavaScript from .NET

过。

Updating the document title

<script src="~/scripts/DocumentInterop.js"></script>

注意一下,竟然可以使用~在路径中。

对JS的交互最早可以在OnAfterRender中执行,因为OnAfterRender只有在客户端执行的时候firstRender才是true,因而有下面的代码:

@inject IJSRuntime JSRuntime
@code {
  [Parameter]
  public string Title { get; set; }

  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    if (firstRender)
      await JSRuntime.InvokeVoidAsync("BlazorUniversity.setDocumentTitle", Title);
  }
}

Passing HTML element references

可以用@ref指示来定义一个ElementReference:

@page "/"

<h1 @ref=MyElementReference>Hello, world!</h1>
Welcome to your new app.

@code {
  ElementReference MyElementReference;
}

实际生成的HTML代码形如:

<h1 _bl_bc0f34fa-16bd-4687-a8eb-9e3838b5170d="">Hello, world!</h1>

一个关于autofocus的案例。HTML页面加载完毕之时会聚焦于第一个具有autofoucs属性的HTML元素之上。可是Blazor是SPA,每次变换HTML的时候并不加载,支持修改URL。所以autofocus的行为不会发生。为了在Blazor中解决这个问题。做法是在OnAfterRenderAsync的时候如果是firstRender,则调用标的上的focus方法。

一个改进的方法是使用将此行为提取成一个AutoFocus组件。

在OnAfterRender之前使用ElementReference上的操作是不合适的,因为元素可能只存在于RenderTree,还没打到DOM上。甚至在那之前,ElementReference都是无效的。下面是Blazor的操作流程:

  • 生成虚拟的渲染树
  • 应用变动到DOM
  • 对于每个标记有@ref的元素,更新组件内对应的EleementReference成员

Calling .NET From JavaScript

只有特点.NET代码才可以在JS中调用:

  • 必须具有JsInvokable属性
  • 必须为public
  • 必须可序列化为JSON
  • 必须返回可序列化为JSON的值
  • JsInvokable属性指定有identifier参数,那么参数值必须唯一

然后.NET对象的引用必须传给JS。但是普通的传值需要经过序列化,所以不可以直接采用。而必须通过DotNetObjectReference.create来创建一个引用之后来传给JS。

可通过invokeMethodAsync方法来调用.NET方法。

Lifetimes and memory leaks

创建DotNetObjectReference而不释放清除会导致内存泄漏。为了解决这个问题,要实现DotNetObjectReference的IDisposable方法:

@implements IDisposable

@code {
    public void Dispose()
  {
    GC.SuppressFinalize(this);

    if (ObjectReference != null)
    {
      //Now dispose our object reference so our component can be garbage collected
      ObjectReference.Dispose();
    }
  }
}
  • GC.SuppressFinalize待考证
  • 看起来ObjectReference不随组件的生命周期自动管理,而是属于组件外部资源,需要一些手动管理。
    • Dispose会使其从JSRuntime移除
  • 假设组件销毁时,由其生成的Web内容也会子自动销毁。
    • 这个假设可能不会成立,可能需要移除JS侧对被销毁方法的使用。

Type safety

JS类型于.NET类型之间的对应关系有限:

JS .NET
boolean System.Boolean
string System.String
number System.Float/System.Decimal或System.Int32(若是整型)
Date System.DateTime或者System.String

JS调用.NET时,对于enum,一般情况下需要传整数值,但是经过[System.Text.Json.Serialization.JsonConverter]修饰之后可以传字符串值,例如:

[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
public enum TestEnum
{
  FirstValue = 100,
  SecondValue = 200
};

对于以上定义,下面两种调用方式皆可:

dotNetObject.invokeMethodAsync('OurInvokableDotNetMethod', 'FirstValue');
dotNetObject.invokeMethodAsync('OurInvokableDotNetMethod', 200);

Calling static .NET methods

在JS侧尚没有方法判断.NET侧是否就绪可以被调用。但是可以通过在JS侧启动Blazor的方式来进行一定的协调。

适合在JS侧调用的.NET静态方法:

  • 静态方法必须时public,且其所在的类也必须时public
  • 对于同步执行的方法,返回值类型必须是void或者可以序列化成JSON的类型
  • 对于异步执行的方法,返回值类型必须是Task或者Task,其中T必须是可以序列化成JSON的
  • 所有的入参必须可以序列化成JSON
  • 方法必须标注有[JSInvokable],若在[JSInvokable]中指定了identifier,其内容不能重复

一个示例:

setTimeout(async function () {
  const settings = await DotNet.invokeMethodAsync("CallingStaticDotNetMethods", "GetSettings");
  alert('API key: ' + settings.someApiKey);
}, 1000);

(本篇完)