带有静态服务器端呈现(静态 SSR)的 ASP.NET Core Blazor JavaScript

本文介绍如何在带有静态服务器端呈现(静态 SSR)和增强型导航的 Blazor Web 应用中加载 JavaScript(JS)。

某些应用依赖于 JS 来执行特定于每个页面的初始化任务。 使用 Blazor 的增强导航功能时(用户可避免重新加载整个页面),每次发生增强的页面导航时,特定于页面的 JS 可能不会按预期再次执行。

为了避免此问题,我们不建议依赖应用于组件的布局文件之外的特定于页面的 <script> 元素。 相反,脚本应注册一个 afterWebStartedJS 初始化程序来执行初始化逻辑,并使用事件侦听器 (blazor.addEventListener("enhancedload", callback)) 来侦听由增强型导航引起的页面更新。

以下示例演示了一种配置 JS 代码的方法,以便代码在最初加载或更新具有增强导航功能的静态呈现页面时运行。

以下 PageWithScript 组件示例是应用中的一个组件,它要求脚本使用静态 SSR 和增强型导航运行。 以下组件示例包括 Razor 类库 (RCL) 中的 PageScript 组件,该组件将在本文后面部分添加到解决方案中。

Components/Pages/PageWithScript.razor

@page "/page-with-script"
@using BlazorPageScript

<PageTitle>Enhanced Load Script Example</PageTitle>

<PageScript Src="./Components/Pages/PageWithScript.razor.js" />

Welcome to my page.

在 Blazor Web 应用中,添加以下并置 JS 文件

  • onLoad 将在脚本添加到页面时调用。
  • onUpdate 将在增强型更新后脚本仍存在于页面时调用。
  • onDispose 将在增强型更新后从页面删除脚本时调用。

Components/Pages/PageWithScript.razor.js

export function onLoad() {
  console.log('Loaded');
}

export function onUpdate() {
  console.log('Updated');
}

export function onDispose() {
  console.log('Disposed');
}

Razor类库 (RCL)(示例 RCL 命名为 BlazorPageScript)中,添加以下模块。

wwwroot/BlazorPageScript.lib.module.js

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
  if (!src) {
    throw new Error('Must provide a non-empty value for the "src" attribute.');
  }

  let pageScriptInfo = pageScriptInfoBySrc.get(src);

  if (pageScriptInfo) {
    pageScriptInfo.referenceCount++;
  } else {
    pageScriptInfo = { referenceCount: 1, module: null };
    pageScriptInfoBySrc.set(src, pageScriptInfo);
    initializePageScriptModule(src, pageScriptInfo);
  }
}

function unregisterPageScriptElement(src) {
  if (!src) {
    return;
  }

  const pageScriptInfo = pageScriptInfoBySrc.get(src);
  
  if (!pageScriptInfo) {
    return;
  }

  pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
  if (src.startsWith("./")) {
    src = new URL(src.substr(2), document.baseURI).toString();
  }

  const module = await import(src);

  if (pageScriptInfo.referenceCount <= 0) {
    return;
  }

  pageScriptInfo.module = module;
  module.onLoad?.();
  module.onUpdate?.();
}

function onEnhancedLoad() {
  for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
    if (referenceCount <= 0) {
      module?.onDispose?.();
      pageScriptInfoBySrc.delete(src);
    }
  }

  for (const { module } of pageScriptInfoBySrc.values()) {
    module?.onUpdate?.();
  }
}

export function afterWebStarted(blazor) {
  customElements.define('page-script', class extends HTMLElement {
    static observedAttributes = ['src'];

    attributeChangedCallback(name, oldValue, newValue) {
      if (name !== 'src') {
        return;
      }

      this.src = newValue;
      unregisterPageScriptElement(oldValue);
      registerPageScriptElement(newValue);
    }

    disconnectedCallback() {
      unregisterPageScriptElement(this.src);
    }
  });

  blazor.addEventListener('enhancedload', onEnhancedLoad);
}

在 RCL 中添加以下 PageScript 组件。

PageScript.razor

<page-script src="@Src"></page-script>

@code {
    [Parameter]
    [EditorRequired]
    public string Src { get; set; } = default!;
}

组件 PageScript 通常在页面的顶层运行。

如果将 PageScript 组件放置在应用布局中(例如 MainLayout.razor)(这会导致使用该布局的页面之间共享 PageScript),则该组件仅在整个页面重新加载后运行 onLoad,并在发生任何增强型页面更新(包括增强型导航)时运行 onUpdate

若要在页面之间重复使用同一模块,但在每个页面更改中调用 onLoadonDispose 回调,请将查询字符串追加到脚本末尾,以便将其识别为其他模块。 应用可以采用将组件名称用作查询字符串值的约定。 在以下示例中,查询字符串为“counter”,因为此 PageScript 组件引用放置在 Counter 组件中。 这只是一个建议,你可以使用你喜欢的任何查询字符串方案。

<PageScript Src="./Components/Pages/PageWithScript.razor.js?counter" />

要监视特定 DOM 元素中的更改,请使用客户端上 JS 中的 MutationObserver 模式。 有关详细信息,请参阅 ASP.NET Core BlazorJavaScript 互操作性(JS 互操作)

不使用 RCL 的示例实现

本文中所述的方法可以直接在 Blazor Web 应用中实现,而无需使用 Razor 类库 (RCL)。 有关示例,请参阅在 ASP.NET Core Web 应用中为 TOTP 验证器应用启用 QR 码生成Blazor