首页  >  文章  >  web前端  >  我用一个简单的 Web 组件替换了 htmx

我用一个简单的 Web 组件替换了 htmx

WBOY
WBOY原创
2024-09-07 06:37:16538浏览

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