在过去的几个月里,我一直在围绕 VueJS 和 Vanilla JavaScript 编写大量文档、教程和练习。就某些情况而言,我是Tech Elevator的课程开发人员,这是一个编码训练营,可在 14 周内教学生如何编码。考虑到这一点,所有内容都面向初学者,但适合所有人。
我最近在做一些关于 Fetch API 的教程和练习,我想整理一个很好的例子来说明如何从本地文件中读取一些 JSON 数据,然后将其添加到页面中。在一个简单的示例中,我将只使用createElement和createTextNode并将项目附加到 DOM。
在一个涉及更多标记的更复杂的示例中,创建元素、节点以及处理属性和类会变得非常麻烦。在这种情况下,一个很好的解决方案是内容元素模板。我还意识到很多开发人员(初学者和老手)可能不知道这是什么或者我们为什么要使用它。
在本文中,我将看看<template>
HTML 和 Vanilla JavaScript 中的标签。当你知道这个标签为什么存在时,为什么它在 Vue 单文件组件中使用可能会更有意义。
内容元素模板
您可以<template></template>
像看待任何其他模板一样看待 HTML 中的标签。模板是一种模具或图案,它为您提供了创建其他东西的起点。MDN 文档将 HTML 内容模板定义为:
HTML 内容模板 (
<template>
) 元素是一种用于保存客户端内容的机制,这些内容在页面加载时不会呈现,但随后可能会在运行时使用 JavaScript 进行实例化。将模板视为存储以供后续在文档中使用的内容片段。虽然解析器
<template>
在加载页面时确实会处理元素的内容,但它这样做只是为了确保这些内容是有效的;但是,不会呈现元素的内容。
这听起来很简单,但如果它不完全有意义,请不要担心。我们将看一个实际的例子,希望它能为我们澄清一切。
HTML 内容模板演示
我将展示如何<template></template>
在 Vanilla JavaScript 中使用标签的内容放在一起。如果你想查看这个演示的源代码,你可以在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>
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"
}
}
第一步是读取 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));
现在我们有了一组用户,我们可以开始使用我们的模板了。首先,我们需要检查用户的浏览器是否支持 HTML Content Template 标签。只要您使用的是现代浏览器,它就应该支持它,但最好进行此检查。
if('content' in document.createElement('template')) {
});
} else {
console.error('Your browser does not support templates');
}
现在我们知道浏览器支持此功能,我们需要获取对父容器的引用,我们将向其附加每个用户卡,在这种情况下,它是具有用户 ID 的元素。然后我们将遍历数组中的每个元素。
if('content' in document.createElement('template')) {
const container = document.getElementById('users');
users.forEach((user) => {
});
} else {
console.error('Your browser does not support templates');
}
在用户数组的每次迭代中,我们将创建模板的副本(克隆)。我们这样做的方法是获取对元素的引用,获取内容(模板标签内的内容),然后克隆它。我们将 true 传递给 cloneNode 方法,以便我们使用深度克隆并使用它抓取所有子节点。
const tmpl = document.getElementById('user-card-template').content.cloneNode(true);
从那里我们可以简单地查询特定元素的模板并将其内容设置为我们从用户数组中读取的值。在大多数情况下,我只是设置元素的内部文本。最后,我们使用对容器元素的引用,并将模板标签内的所有内容附加到我们的页面。
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));
条件句
写完这篇文章后,我的朋友托德问了我一个非常好的问题。
托德·夏普
@recursivecodes
@therealdanvega很棒的文章丹。模板是否支持条件?如果用户没有这些链接之一怎么办?
2019 年 1 月 26 日下午 15:35
我们在这里所做的是克隆模板标签内的标记。因为这是正常的标记,我们可以用它做任何我们想做的事情。因此,假设有些用户在所有社交网络上都有一个帐户,而有些则没有。
"social": {
"github": "github_username",
"reddit": "reddit_username",
"twitter": "twitter_username",
"instagram": "instagram_username",
"facebook": "facebook_username"
}
"social": {
"github": "github_username",
"reddit": "reddit_username",
"twitter": "twitter_username"
}
我们可以在这里做的是遍历我们支持的所有已知社交网络,如果 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();
}
});
Vanilla JavaScript Wrapup 中的 HTML 模板
这就是在标记中创建模板、克隆它并向其中添加数据所需的全部内容。我会提到这一点,因为知道这一点很重要,但是如果您采用这种方法并查看源代码,您将只会看到模板代码。这意味着,如果您有数据需要写入需要搜索引擎友好的页面,这可能不是一个好的解决方案。
Vue中的模板标签
现在我们知道了这个<template></template>
标签是什么,它应该更能理解 Vue 使用它的目的。如果您在 Vue 中创建一个新的单文件组件,您将拥有一些如下所示的代码。
<template>
<div id="users">
<!-- markup here -->
</div>
</template>
<script>
// js here
</script>
<style>
/* css here */
</style>
现在应该很清楚为什么我们需要在模板标签内添加一个顶级元素。Vue 会将模板标签内的所有内容编译到虚拟 DOM 中。Vue 文档将模板语法描述为:
Vue.js 使用基于 HTML 的模板语法,允许您以声明方式将渲染的 DOM 绑定到底层 Vue 实例的数据。所有 Vue.js 模板都是有效的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。
在底层,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应式系统,Vue 能够智能地计算出最少数量的组件来重新渲染,并在应用程序状态发生变化时应用最少的 DOM 操作。
如果您熟悉虚拟 DOM 概念并更喜欢 JavaScript 的原始功能,您还可以直接编写渲染函数而不是模板,并提供可选的 JSX 支持。
结论
如果您之前从未使用过<template></template>
标签,我希望您今天学到了一些新东西。请随时询问有关本文或我构建的演示的任何问题。