(圖片來源: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中文網其他相關文章!