Vanilla JavaScript 和 Vue 中的 HTML 模板标签

2025-06-07

Vanilla JavaScript 和 Vue 中的 HTML 模板标签

过去几个月,我一直在围绕 VueJS 和原生 JavaScript 编写大量文档、教程和练习。简单介绍一下,我是Tech Elevator的课程开发人员,这是一个编程训练营,旨在帮助学生在 14 周内学会编程。因此,所有内容都面向初学者,但同时也适合所有人。

我最近在做一些关于 Fetch API 的教程和练习,想写一个好的例子,演示如何从本地文件读取 JSON 数据并将其添加到页面。一个简单的例子,我只需要使用createElementcreateTextNode 函数,并将元素添加到 DOM 中。

在涉及更多标记的更复杂示例中,创建元素、节点以及处理属性和类可能会变得非常繁琐。在这种情况下,内容元素模板是一个很好的解决方案。我还意识到,很多开发人员(无论是初学者还是老手)可能不知道这是什么,也不知道为什么要使用它。

在本文中,我将探讨<template>HTML 和原生 JavaScript 中的这个标签。了解了这个标签存在的原因后,或许就能更好地理解它在 Vue 单文件组件中的应用。

内容元素模板

你可以像理解<template></template>其他模板一样理解 HTML 中的标签。模板是一种模型或模式,它为你提供了一个起点,让你可以在此基础上创建其他内容。MDN 文档将 HTML 内容模板定义为:

HTML 内容模板(<template>)元素是一种用于保存客户端内容的机制,该内容在页面加载时不会被呈现,但随后可能会在运行时使用 JavaScript 进行实例化。

可以将模板视为存储起来以供后续在文档中使用的一部分内容。虽然解析器<template>在加载页面时会处理元素的内容,但它这样做只是为了确保这些内容有效;元素的内容不会被渲染。

这听起来很简单,但如果还没完全理解也不用担心。我们来看一个实际的例子,希望能帮我们理清思路。

HTML 内容模板演示

我整理了如何在原生 JavaScript 中使用该标签的示例<template></template>。如果您想查看此演示的源代码,可以在Github上找到。我们将构建一个页面,该页面基于一些 JSON 数据加载用户卡片列表,最终效果如下。

演示

标记

正如我之前所说,这个项目的目标是从 JSON 文件中读取一些用户数据,然后将用户信息写入页面。当你需要逐个创建元素并将它们添加到页面时,这会变得非常繁琐。

更好的方法是先定义好标记和 CSS 的样子,然后用模板标签包裹标记。我最终的 HTML 代码如下。完成后,我只需在<template></template>标记周围添加一个标签,并为其指定一个 id 即可。

<template id="user-card-template">
<div class="user">
    <div class="profile">
        <img src="" class="avatar"/>
        <h2></h2>
        <span class="title"></span>
        <div class="social">
            <a href="https://www.github.com" target="_blank"><i class="fab fa-github fa-2x" target="_blank"></i></a>
            <a href="https://www.reddit.com" target="_blank"><i class="fab fa-reddit-alien fa-2x"></i></a>
            <a href="https://www.twitter.com" target="_blank"><i class="fab fa-twitter fa-2x"></i></a>
            <a href="https://www.instagram.com" target="_blank"><i class="fab fa-instagram fa-2x"></i></a>
            <a href="http://www.facebook.com" target="_blank"><i class="fab fa-facebook-f fa-2x"></i></a>
        </div>
    </div>
    <div class="stats">
        <div class="posts">
            <h3></h3>
            <span>Posts</span>
        </div>
        <div class="likes">
            <h3></h3>
            <span>Likes</span>
        </div>
        <div class="followers">
            <h3></h3>
            <span>Followers</span>
        </div>
    </div>
</div>
</template>
Enter fullscreen mode Exit fullscreen mode

JavaScript

现在我已经有了标记,是时候看看 JavaScript 了。我有一个名为 的 JSON 文件,users.json其中包含 9 个用户,如下所示。

{ 
    "id": 1,
    "fullname": "Jonathan Stark",
    "title": "Software Developer",
    "avatar": "img/user_1.png",
    "social": {
        "github": "github_username",
        "reddit": "reddit_username",
        "twitter": "twitter_username",
        "instagram": "instagram_username",
        "facebook": "facebook_username"
    },
    "stats": {
        "posts": "150",
        "likes": "680",
        "followers": "199"
    }
}
Enter fullscreen mode Exit fullscreen mode

第一步是读取 JSON,为此我们将使用 Fetch API。如果你之前使用过 Fetch,那么这应该不是什么新鲜事。

fetch('users.json') 
.then((response) => {
  return response.json();
})
.then((users) => {
  // we have an array of users
})
.catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

现在我们有了用户数组,可以开始使用模板了。首先,我们需要检查用户的浏览器是否支持 HTML 内容模板标签。只要你使用的是现代浏览器,它就应该支持,但最佳做法是进行此项检查。

if('content' in document.createElement('template')) {

});
} else {
    console.error('Your browser does not support templates');
}
Enter fullscreen mode Exit fullscreen mode

现在我们知道浏览器支持这个功能,我们需要获取一个父容器的引用,我们将每个用户卡片都添加到这个容器中,在本例中,它是 id 为 users 的元素。然后,我们将迭代数组中的每个元素。

if('content' in document.createElement('template')) {
    const container = document.getElementById('users');
    users.forEach((user) => {

    });
} else {
    console.error('Your browser does not support templates');
}
Enter fullscreen mode Exit fullscreen mode

在每次迭代用户数组时,我们都会创建模板的副本(克隆)。具体方法是获取元素的引用,获取其内容(模板标签内的内容),然后克隆它。我们将 true 传递给 cloneNode 方法,以便进行深度克隆并获取所有子元素。

const tmpl = document.getElementById('user-card-template').content.cloneNode(true);
Enter fullscreen mode Exit fullscreen mode

从这里,我们可以简单地查询模板中的特定元素,并将其内容设置为我们从用户数组中读取的值。大多数情况下,我只是设置元素的内部文本。最后,我们使用对容器元素的引用,将模板标签内的所有内容附加到页面中。

fetch('users.json') 
.then((response) => {
  return response.json();
})
.then((users) => {
  if('content' in document.createElement('template')) {
    const container = document.getElementById('users');
    users.forEach((user) => {
      const tmpl = document.getElementById('user-card-template').content.cloneNode(true);
      tmpl.querySelector('h2').innerText = user.fullname;
      tmpl.querySelector('.title').innerText = user.title;
      tmpl.querySelector('img').setAttribute("src",user.avatar);
      tmpl.querySelector('.posts h3').innerText = user.stats.posts;
      tmpl.querySelector('.likes h3').innerText = user.stats.likes;
      tmpl.querySelector('.followers h3').innerText = user.stats.followers;
      container.appendChild(tmpl);
    });
  } else {
    console.error('Your browser does not support templates');
  }
})
.catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

条件语句

写完这篇文章后,我的朋友托德问了我一个很好的问题。

我们这里所做的就是克隆模板标签内的标记。因为这是普通的标记,所以我们可以对它进行任何操作。假设有些用户在所有社交网络上都有账户,而有些用户没有。

"social": {
  "github": "github_username",
  "reddit": "reddit_username",
  "twitter": "twitter_username",
  "instagram": "instagram_username",
  "facebook": "facebook_username"
}
Enter fullscreen mode Exit fullscreen mode
"social": {
  "github": "github_username",
  "reddit": "reddit_username",
  "twitter": "twitter_username"
}
Enter fullscreen mode Exit fullscreen mode

我们在这里可以做的是遍历所有已知的我们支持的社交网络,如果 users.social 对象没有该键,我们就从 DOM 中移除该元素。同样,我们这里处理的是普通元素,因此我们可以执行诸如将可见性设置为隐藏或完全移除它们之类的操作。在本例中,我们希望移除它,因为如果只是隐藏它,在某些情况下就会出现空白区域,这看起来不太好。

// this is a list of social networks we display under a users profile
const socialLinks = ['github','reddit','twitter','instagram','facebook']
// iterate over that list and check to see if they have an account on that network
socialLinks.forEach((social) => {
  // if they don't have a link in the JSON data hide that link & icon
  if(!user.social.hasOwnProperty(social)) {
    tmpl.querySelector(`.${social}`).remove();
  }
});
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript 包装中的 HTML 模板

这就是你用标记创建模板、克隆模板并添加数据所需的全部步骤。我之所以提到这一点,是因为它很重要,但如果你采用这种方法并查看源代码,你只会看到模板代码。这意味着,如果你需要将数据写入需要搜索引擎友好的页面,这可能不是一个好的解决方案。

Vue 中的模板标签

现在我们知道了<template></template>标签的含义,应该更容易理解 Vue 使用它的原因了。如果你在 Vue 中创建一个新的单文件组件,你将得到类似这样的代码。

<template>
    <div id="users">
        <!-- markup here -->
    </div>
</template>

<script>
    // js here
</script>

<style>
    /* css here */
</style>
Enter fullscreen mode Exit fullscreen mode

现在应该很清楚为什么我们需要在模板标签内添加一个顶级元素了。Vue 会将模板标签内的所有内容编译到虚拟 DOM 中。Vue 文档将模板语法描述如下:

Vue.js 使用基于 HTML 的模板语法,允许你以声明方式将渲染的 DOM 绑定到底层 Vue 实例的数据。所有 Vue.js 模板都是有效的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。

在底层,Vue 将模板编译为虚拟 DOM 渲染函数。结合响应式系统,Vue 能够智能地计算出需要重新渲染的组件数量,并在应用状态发生变化时应用最少的 DOM 操作。

如果您熟悉虚拟 DOM 概念并更喜欢 JavaScript 的原始功能,您还可以直接编写渲染函数而不是模板,并可选地支持 JSX。

结论

如果您以前从未使用过这个<template></template>标签,我希望您今天能学到一些新东西。如果您对本文或我构建的演示有任何疑问,请随时提问。

文章来源:https://dev.to/therealdanvega/html-template-tag-in-vanilla-javascript-and-vue-5eoo
PREV
成为一名全栈 Web 开发人员有点疯狂
NEXT
欢迎主题 - v79