Share via


具有靜態伺服器端轉譯 (靜態 SSR) 的 ASP.NET Core Blazor JavaScript

本文說明如何在具有靜態伺服器端轉譯 (靜態 SSR) 及增強式導覽的 BlazorWeb 應用程式中載入 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 Blazor JavaScript 互通性 (JS Interop)

不使用 RCL 的範例實作

本文所述的方法可直接在 Blazor Web 應用程式中實作,而不需使用 Razor 類別庫 (RCL)。 如需範例,請參閱在 ASP.NET Core Blazor Web 應用程式中為 TOTP 驗證器應用程式啟用產生 QR 代碼