(图片来源: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 的元素都将被替换。它适用于 元素也是!
该元素主要有两种工作方式:
所以,使用一些像这样的 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中文网其他相关文章!