发布于 2026-01-06 4 阅读
0

什么是 HATEOAS?完整指南 + 使用超媒体构建您自己的应用程序 🔥

什么是 HATEOAS?完整指南 + 使用超媒体构建您自己的应用程序 🔥

大多数人肯定至少接触过一次 HATEOAS 的概念,但它究竟是什么呢?

事实上,这是一个相当古老但非常有趣的话题,即使在今天,它在发展领域也没有失去其相关性。

本文将介绍基础知识,并创建我们的第一个简单应用程序来体现这一主题。

准备好了吗?那我们开始吧!


🔎 这是什么?

很多人会先给出定义,但如果没有一些理解,我们自然就先到此为止吧。让我们先来看看我们的应用程序是如何运作的。

假设我们有一个基于某个流行框架构建的典型单页应用程序(SPA)。让我们回顾一下我们是如何从中检索数据的。

图1

我们从客户端预先定义的 API 路由中检索数据,但如果路由发生变化怎么办?如果我们在一家拥有数千名员工的大公司工作,却永远不知道哪个后端当前是最新的,那该怎么办?

或许现在我们可以给出 HATEOAS 的定义了。

HATEOAS - 超媒体作为应用程序状态的引擎

这意味着我们将采用一条特定的现有路径,并根据响应对象描述该路径的操作。

📦 应用的架构结构

现在,让我们来看一些具体的例子,了解这个概念能为我们的SPA应用带来什么。我们以一个简单的在线服装商店为例。

我们知道产品评论是通过我们指定的 APIreviews/{id}路由获取的,但有一天,他们突然删除了这些评论,并将其集成到了一个评论平台。结果就是,例如,当用户想购买一件夹克时,他们打开评论页面,但页面出现错误,应用停止运行。

针对这种情况,我们可以创建一个不同的应用程序。这里我们不讨论 BFF 层,让我们专注于我们的主题。如果我们创建一个products/{id}路由,并从中检索所需数据呢?让我们看看它在图中会是什么样子:

图 2

我们看到这里有一个可供使用的链接列表。现在,我们可以创建一个按钮,按钮上的链接来自响应数据。点击该按钮,就能跳转到我们需要的路由。这样一来,我们实际上只需要为每个页面设置几个路由,就能构建整个应用程序。

🔧 创建应用程序

现在,我们来为我们的服装店创建一个小型应用程序。功能很简单:点击商品即可获取相关信息。我们将使用 hmpl-js,并通过 hmpl-dom 扩展来处理单个 HTML 文件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Mini HATEOAS Shop</title>
    <style>
      body {
        padding: 20px;
      }
      .item {
        padding: 8px;
        border: 1px solid #ddd;
        margin: 6px 0;
        cursor: pointer;
        border-radius: 6px;
      }
      .back {
        color: blue;
        cursor: pointer;
        margin-top: 10px;
        display: inline-block;
      }
      pre {
        background: #f6f6f6;
        padding: 8px;
        border-radius: 6px;
      }
    </style>

    <script>
      (function () {
        const products = [
          { id: 1, name: "T-Shirt", price: 15 },
          { id: 2, name: "Hoodie", price: 40 },
          { id: 3, name: "Jeans", price: 55 },
        ];
        const real = fetch.bind(window);

        window.fetch = async (input, init) => {
          const url = typeof input === "string" ? input : input?.url || "";
          await new Promise((r) => setTimeout(r, 80));

          if (url.endsWith("/api/products")) {
            return new Response(
              products
                .map(
                  (p) => `
        <div class="item" data-id="${p.id}">
          <strong>${p.name}</strong> — $${p.price}
        </div>`
                )
                .join(""),
              { status: 200, headers: { "Content-Type": "text/html" } }
            );
          }

          if (/\/api\/product\/\d+$/.test(url)) {
            const id = +url.split("/").pop();
            const p = products.find((x) => x.id === id);
            return new Response(
              `
        <h2>${p.name}</h2>
        <p>Price: $${p.price}</p>
        <div class="back" data-back="/api/products">← Back</div>
        <pre data-hateoas>${JSON.stringify(
          {
            self: `/api/product/${p.id}`,
            add_to_cart: `/api/cart/add/${p.id}`,
          },
          null,
          2
        )}</pre>
      `,
              { status: 200, headers: { "Content-Type": "text/html" } }
            );
          }

          return real(input, init);
        };
      })();
    </script>
  </head>

  <body>
    <main>
      <template data-hmpl>
        <div>
          <h1>Clothing Shop — HATEOAS</h1>

          <div id="list">
            {{#request src="/api/products"}} 
              {{#indicator trigger="pending"}}Loading…{{/indicator}}
            {{/request}}
          </div>

          <div id="detail"></div>

          <h3>Info:</h3>
          <pre id="info">none</pre>
        </div>
      </template>
    </main>

    <script src="https://unpkg.com/json5/dist/index.min.js"></script>
    <script src="https://unpkg.com/dompurify/dist/purify.min.js"></script>
    <script src="https://unpkg.com/hmpl-js/dist/hmpl.min.js"></script>
    <script src="https://unpkg.com/hmpl-dom/dist/hmpl-dom.min.js"></script>

    <script>
      document.addEventListener("click", async (e) => {
        const item = e.target.closest("[data-id]");
        if (item) {
          const id = item.dataset.id;
          const html = await (await fetch("/api/product/" + id)).text();
          detail.innerHTML = html;
          info.textContent =
            detail.querySelector("[data-hateoas]")?.textContent || "none";
        }

        const back = e.target.closest("[data-back]");
        if (back) {
          const html = await (await fetch(back.dataset.back)).text();
          list.innerHTML = html;
          detail.innerHTML = "";
        }
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

因此,我们的应用程序只有大约 120 行代码,代码如下所示:

应用程序

如您所见,得益于hmpl-js和 HATEOAS 架构,我们只需几十行代码即可创建酷炫且功能强大的应用程序,而且由于我们没有设置严格的路由,因此该应用程序可以非常灵活地与模拟服务器一起使用。

✅ 结论

本文探讨了HATEOAS架构,并使用该架构创建了一个小型应用程序。事实上,HATEOAS架构在应用程序开发领域并未受到太多关注,这实属不妥,因为其灵活性使我们能够创建更具可扩展性和易于维护的网站。


非常感谢您阅读这篇文章❤️!

你觉得这种建筑风格怎么样?你是否在自己的项目中运用过,或者在其他项目中见过?欢迎在评论区留言!

PS:也别忘了给HMPL点个赞哦!

🌱 星级 HMPL

文章来源:https://dev.to/anthonymax/what-is-hateoas-a-complete-guide-build-your-own-app-using-hypermedia-k56