ASP.NET Core Blazor JavaScript ze statycznym renderowaniem po stronie serwera (statyczne SSR)

W tym artykule wyjaśniono, jak załadować kod JavaScript (JS) w Blazor aplikacji internetowej ze statycznym renderowaniem po stronie serwera (statycznym usługą SSR) i rozszerzoną nawigacją.

Niektóre aplikacje zależą od JS wykonywania zadań inicjowania specyficznych dla każdej strony. W przypadku korzystania z Blazorrozszerzonej funkcji nawigacji, która pozwala użytkownikowi uniknąć ponownego ładowania całej strony, szczegółowe informacje dotyczące JS strony mogą nie być wykonywane ponownie zgodnie z oczekiwaniami za każdym razem, gdy wystąpi ulepszona nawigacja po stronie.

Aby uniknąć tego problemu, nie zalecamy polegania na elementach specyficznych dla <script> strony umieszczonych poza plikiem układu zastosowanym do składnika. Zamiast tego skrypty powinny zarejestrować afterWebStartedJS inicjator do wykonywania logiki inicjowania i używać odbiornika zdarzeń (blazor.addEventListener("enhancedload", callback)) do nasłuchiwania aktualizacji stron spowodowanych przez rozszerzoną nawigację.

W poniższym przykładzie pokazano jeden ze sposobów konfigurowania JS kodu do uruchomienia, gdy statycznie renderowana strona z rozszerzonym nawigacją jest początkowo ładowana lub aktualizowana.

Poniższy PageWithScript przykładowy składnik to składnik w aplikacji, który wymaga uruchamiania skryptów przy użyciu statycznego przewodnika SSR i rozszerzonej nawigacji. Poniższy przykład składnika zawiera PageScript składnik z Razor biblioteki klas (RCL), który jest dodawany do rozwiązania w dalszej części tego artykułu.

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.

W aplikacji Blazor internetowej dodaj następujący collocated JS plik:

  • onLoad jest wywoływany po dodaniu skryptu do strony.
  • onUpdate jest wywoływany, gdy skrypt nadal istnieje na stronie po rozszerzonej aktualizacji.
  • onDispose jest wywoływany po usunięciu skryptu ze strony po rozszerzonej aktualizacji.

Components/Pages/PageWithScript.razor.js:

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

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

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

W bibliotece Razor klas (RCL) (przykładowa lista RCL nosi nazwę BlazorPageScript), dodaj następujący moduł.

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

Na liście RCL dodaj następujący PageScript składnik.

PageScript.razor:

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

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

Składnik PageScript działa normalnie na najwyższym poziomie strony.

Jeśli umieścisz PageScript składnik w układzie aplikacji (na przykład MainLayout.razor), co spowoduje udostępnienie PageScript go między stronami korzystającymi z układu, składnik jest uruchamiany onLoad tylko po ponownym załadowaniu pełnej strony i onUpdate w przypadku wystąpienia dowolnej rozszerzonej aktualizacji strony, w tym rozszerzonej nawigacji.

Aby ponownie użyć tego samego modułu między stronami, ale wywołania onLoad zwrotne i onDispose są wywoływane na każdej stronie, dołącz ciąg zapytania na końcu skryptu, aby był rozpoznawany jako inny moduł. Aplikacja może przyjąć konwencję używania nazwy składnika jako wartości ciągu zapytania. W poniższym przykładzie ciąg zapytania to "counter", ponieważ odwołanie do tego PageScript składnika jest umieszczone w składniku Counter . Jest to tylko sugestia i można użyć dowolnego preferowanego schematu ciągów zapytania.

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

Aby monitorować zmiany w określonych elementach DOM, należy użyć MutationObserver wzorca w JS kliencie. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Przykładowa implementacja bez używania listy RCL

Podejście opisane w tym artykule można zaimplementować bezpośrednio w Blazor aplikacji internetowej bez używania Razor biblioteki klas (RCL). Aby zapoznać się z przykładem, zobacz Włączanie generowania kodu QR dla aplikacji uwierzytelniania TOTP w aplikacji internetowej platformy ASP.NET CoreBlazor.