原生 JavaScript 和 HTML - 无需框架,无需库。没问题。

2025-05-26

原生 JavaScript 和 HTML - 无需框架,无需库。没问题。

您是否正在使用 Vue、React、Angular 或 Svelte 来创建 Web 应用?我正在使用,如果您也在使用,我敢打赌,您已经很久没有编写过不使用它们来渲染内容的应用了。

仅使用浏览器自带的工具。几年前,我们很多人就是这么编写 Web 应用的。虽然如今的工具有助于简化这一过程(并带来了许多其他好处),但了解底层发生的事情仍然很有用。

另外,如果您要渲染少量内容,可能需要使用 HTML、JavaScript 和 DOM,而无需任何其他工具。最近,我编写了一些 Web 基础示例,以帮助教授 DOM、HTML、JavaScript 和浏览器基础知识。这段经历让我意识到,也许其他开发者,也许就是你,会希望有人能提醒我如何在没有库的情况下渲染内容。

无论如何,它很有趣,很有教育意义,并且可能会让我们都尊重为我们处理这个问题的现代图书馆和框架的价值。

好了,让我们来探索几种不同的内容渲染方式。对了,别忘了把MDN Web 文档放在手边!

完成后,您可以使用 lit-html 将 HTML 应用推送到云端,尽享其全部功能!我提供了一个Azure 免费试用版链接,您可以亲自尝试一下。

示例应用程序

以下是我将在本文中演示的应用。它会获取英雄列表,并在你点击按钮时渲染它们。在获取过程中,它还会渲染一个进度指示器。

英雄之旅

工具

合适的工具很重要,在这个练习中,我们想使用我们现有的基本工具将一些内容渲染到浏览器。不需要框架,不需要库,没问题。

我们只需要用到 HTML、TypeScript /JavaScript、CSS 和浏览器 DOM(文档对象模型)。现在让我们开始工作,看看如何渲染 HTML。

您可以在此处了解有关使用 VS Code 进行 TypeScript 编码的更多信息

如果你仔细观察,你可能会发现我在代码中使用了TypeScript。我使用它是因为它在避免由于类型问题而导致的 bug 方面非常有效,而且我可以利用它转换成我需要的任何 JavaScript 格式的功能。如果你愿意,你也可以完全使用 JavaScript。我很快会在以后的文章中详细介绍如何转换 TypeScript

方法

这个示例应用渲染了一个英雄列表和一个进度指示器(在运行时)。让我们先来探索其中比较简单的一个——进度指示器,并展示它的各种渲染方式。然后,我们将转到英雄列表,看看当需要渲染的内容更多时,情况会如何变化(或保持不变)。

渲染进度指示器

当应用确定要渲染哪些英雄时,进度指示器应该会出现。因此,在用户点击刷新按钮之前,它不会显示,然后才会出现,并在英雄渲染完成后消失。让我们来探索一下实现这一功能的几种方法。

所有这些技术都需要在构建元素时了解它们的放置位置。例如,进度条和英雄列表都需要放在某个地方。一种方法是引用一个将包含它们的现有元素。另一种方法是引用一个元素,并用新内容替换该元素。我们将在本文中逐一介绍这两种方法。

使用内部 HTML 渲染进度

创建 HTML 并将其放入另一个元素是最古老的技术之一,但嘿,它真的有效!找到进度指示器所在的元素。然后将其innerHtml属性设置为新内容。注意,我们还尝试使用允许其跨越多行的模板字符串来使其更易于阅读。

const heroPlaceholder = document.querySelector('.hero-list');
heroPlaceholder.innerHTML = `
  <progress
    class="hero-list progress is-medium is-info" max="100"
  ></progress>
`;
Enter fullscreen mode Exit fullscreen mode

简单。容易。为什么不是所有代码都这样?我的意思是,看看这段代码解决问题有多快!

好吧,你可能已经在思考这段代码有多脆弱了。HTML 中只要有一个错误,砰!问题就来了。它真的可读吗?这段代码可以说可读,但当 HTML 变得更加复杂,有 20 行 HTML 代码,还有类、属性、值等等……你懂的。是的,这并不理想。但对于短小的 HTML 代码来说,它确实有效,而且我不会为了一行代码就轻易放弃它。

另外需要考虑的是嵌入内容如何提升代码的可读性和稳定性。例如,如果您在进度条中添加内容以显示不断变化的加载消息,则可以使用类似这样的替换技术${message}。这本身无所谓好坏,只是在创建大型模板字符串时需要更多考虑。

最后一点innerHTML是,渲染性能“可能”会令人担忧。我之所以这么说,是因为我并不赞同过度优化。但使用 JavaScript 渲染 HTML 的性能还是值得测试的,innerHTML因为它可能会导致浏览器中出现渲染和布局循环。所以请持续关注。

使用 DOM API 渲染进度

减少长字符串带来的混乱的一种方法是使用 DOM API。在这里,您可以创建一个元素,添加所需的类,添加属性,设置值,然后将其添加到 DOM 中。

const heroPlaceholder = document.querySelector('.hero-list');
const progressEl = document.createElement('progress');
progressEl.classList.add('hero-list', 'progress', 'is-medium', 'is-info');
const maxAttr = document.createAttribute('max');
maxAttr.value = '100';
progressEl.setAttributeNode(maxAttr);
heroPlaceholder.replaceWith(progressEl);
Enter fullscreen mode Exit fullscreen mode

这样做的好处是代码需要输入更多代码,并且依赖于 API。换句话说,如果输入错误,相比于技术本身,这种代码更有可能innerHTML抛出错误,从而导致问题和解决方案。

这里的缺点是,用一行代码就能渲染出六行代码innerHTML

DOM API 代码比innerHTML渲染进度条的技术更易读吗?我认为并非如此。但这是因为这个进度条 HTML 非常短小精悍吗?或许吧。如果 HTML 代码只有 20 行,innerHTML阅读起来会更困难……但 DOM API 代码也是如此。

使用模板渲染进度

另一种技术是创建一个<template>标签并使用它来更轻松地呈现内容。

首先创建一个<template>并赋予它一个id。该模板不会在 HTML 页面中渲染,但您可以引用其内容并在稍后使用。这非常有用,因为您可以在最合适的地方编写 HTML:在 HTML 页面中,并充分利用 HTML 编辑器的所有实用功能。

<template id="progress-template">
  <progress class="hero-list progress is-medium is-info" max="100"></progress>
</template>
Enter fullscreen mode Exit fullscreen mode

然后,代码可以使用document.importNode()DOM API 的方法获取模板。如果需要,它可以操作模板中的内容(在本例中不需要)。然后将该内容添加到 DOM 中以渲染进度条。

const heroPlaceholder = document.querySelector('.hero-list');
const template = document.getElementById('progress-template') as HTMLTemplateElement;
const fetchingNode = document.importNode(template.content, true);
heroPlaceholder.replaceWith(fetchingNode);
Enter fullscreen mode Exit fullscreen mode

模板是构建 HTML 的强大方法。我喜欢这种技术,因为它可以让你在合理的位置编写 HTML,而 TypeScript/JavaScript 代码则执行得更少。

您可能想知道是否可以从其他文件导入模板。答案是可以,但需要使用其他库。但本练习坚持不使用任何库。多年来,关于 HTML 导入的讨论一直存在,但正如您在“我可以使用吗”网站中看到的,大多数现代浏览器尚未完全支持。

渲染英雄列表

让我们转到如何使用这三种技术来渲染英雄列表。这里与渲染单个<progress>HTML 元素和渲染英雄列表的区别在于,我们现在渲染的是:

  • 多个 HTML 元素
  • 添加多个类
  • 添加子元素,特定序列
  • 为每个英雄渲染大量相似的内容
  • 在元素内显示动态文本

使用内部 HTML 渲染 Heroes

使用innerHTML它首先从英雄数组开始,然后对每个英雄进行迭代,这样做是合理的。这将有助于逐行构建。除了英雄的名称和描述(我们可以使用模板字符串插入)之外,其他行完全相同。数组中的每个英雄都会构建一个<li>,它映射到一个rows数组。最后,将 rows 数组转换为原始 HTML,用 包裹<ul>,并设置为innerHTML

function createListWithInnerHTML(heroes: Hero[]) {
  const rows = heroes.map(hero => {
    return `<li>
        <div class="card">
          <div class="card-content">
            <div class="content">
              <div class="name">${hero.name}</div>
              <div class="description">${hero.description}</div>
            </div>
          </div>
        </div>
      </li>`;
  });
  const html = `<ul>${rows.join()}</ul>`;
  heroPlaceholder.innerHTML = html;
Enter fullscreen mode Exit fullscreen mode

再次强调,这确实有效。而且它“可以说”相当可读。它的性能是不是最高?它是否充满了不易(或永远)发现的打字错误?你自己判断吧。我们先等等看,看看有没有其他技巧。

使用 DOM API 渲染英雄

function createListWithDOMAPI(heroes: Hero[]) {
  const ul = document.createElement('ul');
  ul.classList.add('list', 'hero-list');
  heroes.forEach(hero => {
    const li = document.createElement('li');
    const card = document.createElement('div');
    card.classList.add('card');
    li.appendChild(card);
    const cardContent = document.createElement('div');
    cardContent.classList.add('card-content');
    card.appendChild(cardContent);
    const content = document.createElement('div');
    content.classList.add('content');
    cardContent.appendChild(content);
    const name = document.createElement('div');
    name.classList.add('name');
    name.textContent = hero.name;
    cardContent.appendChild(name);
    const description = document.createElement('div');
    description.classList.add('description');
    description.textContent = hero.description;
    cardContent.appendChild(description);
    ul.appendChild(li);
  });
  heroPlaceholder.replaceWith(ul);
}
Enter fullscreen mode Exit fullscreen mode

使用模板渲染 Heroes

英雄列表可以使用模板来渲染,使用的技术与我们渲染<progress>元素时相同。首先,在 HTML 页面中创建模板。这个 HTML 比我们在元素中看到的稍微复杂一些。你可以很容易地想象,使用这种技术,即使更多的 HTML 代码也不会有问题。它只是 HTML 页面中的 HTML,同时还具有使用VS Code<progress>等优秀编辑器修复错误和格式化的所有好处

<template id="hero-template">
  <li>
    <div class="card">
      <div class="card-content">
        <div class="content">
          <div class="name"></div>
          <div class="description"></div>
        </div>
      </div>
    </div>
  </li>
</template>
Enter fullscreen mode Exit fullscreen mode

然后,您可以编写代码来创建英雄列表,首先创建一个<ul>来包装英雄<li>行。然后,您可以遍历该heroes数组,并为每个英雄获取相同的模板,document.importNode()再次使用该方法。注意,该模板可以重复用于创建每一行。一个模板将成为您所需行数的蓝图。

英雄列表应该显示每个英雄的名称和描述。这意味着模板只能帮你做到这一点,之后你必须替换模板内部英雄的具体值。这时,有必要找到某种方法来识别和引用放置这些英雄具体值的位置。在本例中,代码querySelector('your-selector')在设置名称和描述之前,使用该方法获取每个英雄的引用。

function createListWithTemplate(heroes: Hero[]) {
  const ul = document.createElement('ul');
  ul.classList.add('list', 'hero-list');
  const template = document.getElementById('hero-template') as HTMLTemplateElement;
  heroes.forEach((hero: Hero) => {
    const heroCard = document.importNode(template.content, true);
    heroCard.querySelector('.description').textContent = hero.description;
    heroCard.querySelector('.name').textContent = hero.name;
    ul.appendChild(heroCard);
  });
  heroPlaceholder.replaceWith(ul);
}
Enter fullscreen mode Exit fullscreen mode

这个模板比其他技术更容易吗?我认为“容易”是相对的。我觉得这段代码遵循了一种模式,这种模式比其他技术更容易重复、更易读,而且不容易出错。

概括

请注意,我还没有提到主流框架和库如何处理内容渲染。Vue、React、Angular 和 Svelte 都使用更少的代码显著简化了渲染过程。除了渲染之外,它们还各自拥有其他优势。本文仅关注使用纯 HTML 和 TypeScript/JavaScript 的 DOM 进行相对简单的渲染。

这给我们带来了什么影响?

希望以上内容能让你了解如何在不使用任何库的情况下渲染内容。还有其他方法吗?当然有。你能创建可重用的函数,使代码更直观、更易重用吗?当然可以。但在某些时候,你可能想尝试一些非常优秀的框架工具,比如 Vue、React、Angular 或 Svelte。

这些流行的框架为我们做了很多事情。如果您和我一样,还记得使用纯 DOM 代码和 JavaScript 或 jQuery 来渲染内容。或者您还记得使用 Handlebars 或 Mustache 等工具来渲染内容。又或许您从未在没有框架的情况下渲染过 DOM。您可以想象一下,如今的库和框架在幕后是如何为您渲染内容的。

即使您使用的框架将其抽象出来,了解使用纯 HTML、TypeScript/JavaScript 和 CSS 可以做什么也是很有帮助的。

无论您的经验如何,我希望简要探索一些可用于呈现内容的技术对您有所帮助。

文章来源:https://dev.to/pluralsight/vanilla-javascript-and-html-no-frameworks-no-libraries-no-problem-2n99
PREV
我应该使用哪个 Linux 发行版?
NEXT
使用 Raspberry Pi 作为开发服务器