首頁 >web前端 >js教程 >我用一個簡單的 Web 元件替換了 htmx

我用一個簡單的 Web 元件替換了 htmx

WBOY
WBOY原創
2024-09-07 06:37:16555瀏覽

I replaced htmx with a simple web component

(圖片來源:https://www.maicar.com/GML/Ajax1.html)

我最近在Mastodon 上進行了一次關於我如何使用htmx 取得巨大成功的對話,有人在我的提及中向我提出了挑戰,以及考慮到我使用htmx 的用途,htmx 實際上是一個相當嚴重的依賴項。他們將我連結到這篇文章和所有內容。

一開始,我有點生氣。我認為我在保持輕量級方面做得很好,htmx 為我提供了很好的幫助,但後來我戴上了我一直試圖戴上的帽子,以重塑我的Web 開發方式: 我的猜測對嗎?我可以做得更好嗎?

所以我繼續用一個小型的 100 行 vanillajs Web 元件取代了我對 htmx 的全部使用,我將把它完整地包含在這篇文章中:

export class AjaxIt extends HTMLElement {
  constructor() {
    super();
    this.addEventListener("submit", this.#handleSubmit);
    this.addEventListener("click", this.#handleClick);
  }

  #handleSubmit(e: SubmitEvent) {
    const form = e.target as HTMLFormElement;
    if (form.parentElement !== this) return;
    e.preventDefault();
    const beforeEv = new CustomEvent("ajax-it:beforeRequest", {
      bubbles: true,
      composed: true,
      cancelable: true,
    });
    form.dispatchEvent(beforeEv);
    if (beforeEv.defaultPrevented) {
      return;
    }
    const data = new FormData(form);
    form.dispatchEvent(new CustomEvent("ajax-it:beforeSend", { bubbles: true, composed: true }));
    const action = (e.submitter as HTMLButtonElement | null)?.formAction || form.action;
    (async () => {
      try {
        const res = await fetch(action, {
          method: form.method || "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            "Ajax-It": "true",
          },
          body: new URLSearchParams(data as unknown as Record<string, string>),
        });
        if (!res.ok) {
          throw new Error("request failed");
        }
        form.dispatchEvent(new CustomEvent("ajax-it:afterRequest", { bubbles: true, composed: true }));
        const text = await res.text();
        this.#injectReplacements(text, new URL(res.url).hash);
      } catch {
        form.dispatchEvent(new CustomEvent("ajax-it:requestFailed", { bubbles: true, composed: true }));
      }
    })();
  }

  #handleClick(e: MouseEvent) {
    const anchor = e.target as HTMLAnchorElement;
    if (anchor.tagName !== "A" || anchor.parentElement !== this) return;
    e.preventDefault();
    anchor.dispatchEvent(new CustomEvent("ajax-it:beforeRequest", { bubbles: true, composed: true }));
    anchor.dispatchEvent(new CustomEvent("ajax-it:beforeSend", { bubbles: true, composed: true }));
    (async () => {
      try {
        const res = await fetch(anchor.href, {
          method: "GET",
          headers: {
            "Ajax-It": "true",
          },
        });
        if (!res.ok) {
          throw new Error("request failed");
        }
        anchor.dispatchEvent(new CustomEvent("ajax-it:afterRequest", { bubbles: true, composed: true }));
        const text = await res.text();
        this.#injectReplacements(text, new URL(res.url).hash);
      } catch {
        anchor.dispatchEvent(new CustomEvent("ajax-it:requestFailed", { bubbles: true, composed: true }));
      }
    })();
  }

  #injectReplacements(html: string, hash: string) {
    setTimeout(() => {
      const div = document.createElement("div");
      div.innerHTML = html;
      const mainTargetConsumed = !!hash && !!div.querySelector(
        hash,
      );
      const elements = [...div.querySelectorAll("[id]") ?? []];
      for (const element of elements.reverse()) {
        // If we have a parent that's already going to replace us, don't bother,
        // it will be dragged in when we replace the ancestor.
        const parentWithID = element.parentElement?.closest("[id]");
        if (parentWithID && document.getElementById(parentWithID.id)) {
          continue;
        }
        document.getElementById(element.id)?.replaceWith(element);
      }
      if (mainTargetConsumed) return;
      if (hash) {
        document
          .querySelector(hash)
          ?.replaceWith(...div.childNodes || []);
      }
    });
  }
}
customElements.define("ajax-it", AjaxIt);

你可以像這樣使用它:

<ajax-it>
  <form action="/some/url">
    <input name=name>
  </form>
</ajax-it>

就是這樣!當回應返回時,回應中包含的任何具有 id 的元素都將被替換。它適用於 元素也是!

此元素主要有兩種工作方式:

  1. 如果您的操作或 href 包含哈希,則當前頁面上或當前頁面上具有與該哈希匹配的 id 的元素將被替換為整個回應的內容。
  2. 如果您傳回的 html 包含本身俱有 ID 的元素,並且這些 ID 在目前文件中具有匹配項,則這些元素將首先被替換(並從上面的「整個回應」替換中排除)。這本質上就是「帶外」交換(又稱 hx-swap-oob)的方式。

所以,用一些像這樣的 html:

<div id=extra-stuff></div>
<div id=user-list></div>

<ajax-it>
  <a href="/users/list#put-it-here">
    Get users
  </a>
</ajax-it>

以及像這樣的伺服器回應:

<ul>
  <li>user 1
  <li>user 2
</ul>

你最終會得到:

<ul> <li>user 1 <li>user 2 </ul> Get users

但是如果您的答案是:

<ul>
  <li>user 1
  <li>user 2
</ul>

Hello, I'm out-of-band

你最終會得到:

Hello, I'm out-of-band

<ul> <li>user 1 <li>user 2 </ul> Get users

...將 id=extra-stuff 交換到帶外,並將

    正常交換。

    為了保持冪等性,我不傾向於使用雜湊版本的東西,只是確保我的所有回應元素都附加了 ID:

<ul id=user-list>
  <li>user 1
  <li>user 2
</ul>

<p id=extra-stuff>Hello, I'm out-of-band</p>

這將維持

以上是我用一個簡單的 Web 元件替換了 htmx的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn