> 웹 프론트엔드 > JS 튜토리얼 > htmx를 간단한 웹 구성 요소로 대체했습니다.

htmx를 간단한 웹 구성 요소로 대체했습니다.

WBOY
풀어 주다: 2024-09-07 06:37:16
원래의
553명이 탐색했습니다.

I replaced htmx with a simple web component

(이미지 출처: https://www.maicar.com/GML/Ajax1.html)

최근 Mastodon에서 내가 htmx를 어떻게 사용하여 큰 성공을 거두었는지에 대한 대화를 나눴는데, 누군가가 이에 대해 나에게 도전하는 내용을 언급했고, 내가 사용하는 목적을 고려할 때 htmx가 실제로 얼마나 큰 의존성을 가지고 있는지에 대해 이야기했습니다. 그들은 저를 이 게시물과 모든 것에 연결해 주었습니다.

처음에는 좀 짜증이 났어요. 나는 일을 가볍게 유지하는 일을 꽤 잘하고 있다고 생각했고 htmx가 나에게 많은 도움을 주었지만 웹 개발 방식을 재창조하는 데 있어서는 지금까지 계속 착용하려고 했던 모자를 썼습니다. : 내 가정이 맞나요? 더 잘할 수 있을까요?

그래서 저는 htmx의 전체 사용을 작은 100줄 바닐라 js 웹 구성 요소로 대체했습니다. 이 구성 요소 전체를 이 게시물에 포함하겠습니다.

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>

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가 대역 외에서 교체되고

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿