web component 【Template】 创建自己的简单SPA应用

看到web component这么火爆,抽空写一篇关于template的博文,怎样创建一个简单的SPA应用。

什么是Template

在web component 中,template和它的名字一样,是一个模板标签,在它创建的上下文中,浏览器是不会去解析的,甚至不会去加载里面的任何资源,浏览器dom解析到template标签就跳过了,比如我们这样写 :

<template>
    <img src='http://localhost:9999/imgs/icon.png' />
</template>

这段代码是要求发送一个请求至http://localhost:9999/imgs/icon.png,得到一张图片,而事实并不像我们想的这样,因为它被包含于Template的上下文中,浏览器根本就不会去理会。

那么这样做的好处在哪儿,我们为什么需要Template标签

因为template标签的特性,我们甚至可以创建一大堆的视图模板,根据需求渲染相应的视图模板,而不必担心浏览器渲染的性能问题,因为Template是不会被浏览器渲染的,我们可以写一个js脚本来让它根据需求进行渲染,实现一个懒加载的效果。

如何根据template的特性创建一个简单的SPA应用

现在来进入今天的正题,以上简单的带过了一下template,可能大家对Template已经有了一个大概的轮廓,别急我们这就来进入实战,趁热打铁吧。

在实际的开发中,我们很多情况是需要多个视图模板的,这里作为demo我创建了两个视图模板,同时还有两个按钮,点击相应的按钮加载相应的视图模板。

整个demo大概是这样的

这里写图片描述

用一个h1标签显示index表示这是主页,随后有两个按钮,zhangsan 和 lisi

点击zhangsan,显示zhangsan的相关信息

这里写图片描述

点击lisi则显示lisi的相关信息

这里写图片描述

这样的一个简单的应用用的是传统pjax,即history API + Ajax 应用,不过源于作为演示,并没有搭建服务器后端,因此主要作用于history API。

现在贴上html部分源码

<template id="zhangsanTpl">
        <h1>i'm ZhangSan</h1>
        <ul>
            <li>姓名:ZhangSan</li>
            <li>年龄:20</li>
            <li>性别: 男</li>
        </ul>
    </template>
    <template id="lisiTpl">
        <h1>i'm Lisi</h1>
        <ul>
            <li>姓名:Lisi</li>
            <li>年龄:18</li>
            <li>性别: 男</li>
        </ul>
    </template>
    <div class="target">
        <h1>Index</h1>
    </div>
    <div>
        <button onclick="targetZhangsan()">zhangsan</button>
        <button onclick="targetLisi()">lisi</button>
    </div>

这里用了两个template表示两个视图模板,分别封装了zhangsan和lisi的相关信息,介于template上下文中的节点在浏览器中是不会被渲染出来的,所以,我们还需要一个js脚本来帮我们做这些事情。

let targetDiv = document.querySelector(".target");
//清除显示容器
let cleanTargetDiv = () => targetDiv.innerHTML = "";
//添加模板到容器
let appendTpl = tpl => targetDiv.appendChild(document.importNode(tpl.content,true));
//改变路由
let updateUrl = url => {
    if(Object.prototype.toString.call(url) == '[object String]'){
        history.pushState({},url,`/${url}`);
    }
    else {
        throw new Error('url must be a string');
    }
}
//初始化路由
updateUrl("host");

//zhangsan点击回调
let targetZhangsan = () => {
    cleanTargetDiv();
    updateUrl('zhangsan');
    appendTpl(document.querySelector('#zhangsanTpl'));
}
//lisi点击回调
let targetLisi = () => {
    cleanTargetDiv();
    updateUrl('lisi');
    appendTpl(document.querySelector('#lisiTpl'));
}

有点懵?别急,我一步一步的来说

首先我们需要拿到的是目标div的DOM对象,也就是我们需要渲染的容器

let targetDiv = document.querySelector(".target");

随后我们这里定义了两个方法用于,清除容器当前内容和添加视图模板到容器(可以简单的理解为显示当前视图模板)

//清除显示容器
let cleanTargetDiv = () => targetDiv.innerHTML = "";
//添加模板到容器
let appendTpl = tpl => targetDiv.appendChild(document.importNode(tpl.content,true));

接下来的不可少的一步是更新路由,我们需要实现点击相关按钮,更新容器内容的同时还要更新路由。

比如我们上面点击zhangsan,路由就变为http://127.0.0.1:8020/zhangsan

点击lisi就变为http://127.0.0.1:8020/lisi

//改变路由
let updateUrl = url => {
    if(Object.prototype.toString.call(url) == '[object String]'){
        history.pushState({},url,`/${url}`);
    }
    else {
        throw new Error('url must be a string');
    }
}

这个方法接收一个参数,这里参数的命名用url表现的有点唐突,因为这里我们需要传入的仅仅只是一个相关路由,而不是整个url,但是由于时间关系,没有做修改,没事将就看。

在方法内部,我们做了一个判断,判断传入参数的类型是否是一个字符串,添加这个限制的原因,是由于传入错误类型导致整个应用崩溃,这“多此一举”主要归功于js的鸭子类型,但是为什么不用TS来写?有类型限制,类型声明不是更好?但是,由于本篇博文讲解的是用原生js来实现,因此这里避开使用TS吧。

如果参数类型不是一个字符串则抛出一个异常。异常信息为’url must be a string’。

//初始化路由
updateUrl("host");

上面这句话是表明刚进入应用时的路由变更为http://127.0.0.1:8020/host,更加具有语义性。

//zhangsan点击回调
let targetZhangsan = () => {
    cleanTargetDiv();
    updateUrl('zhangsan');
    appendTpl(document.querySelector('#zhangsanTpl'));
}
//lisi点击回调
let targetLisi = () => {
    cleanTargetDiv();
    updateUrl('lisi');
    appendTpl(document.querySelector('#lisiTpl'));
}

上面是为了给两个按钮绑定监听事件,并在元素上进行声明绑定

<button onclick="targetZhangsan()">zhangsan</button>
<button onclick="targetLisi()">lisi</button>

回调函数里的三条语句并不难,调用清除当前容器的方法和更新路由的方法。

重点在第三句。

//lisi

appendTpl(document.querySelector('#lisiTpl'));

//zhangsan

appendTpl(document.querySelector('#zhangsanTpl'));

现在回头看看上面的方法声明

//添加模板到容器
let appendTpl = tpl => targetDiv.appendChild(document.importNode(tpl.content,true));

这个方法首先拿到了目标容器(.target)的DOM对象,随后向用appendChild添加子元素,而添加的子元素是

document.importNode(tpl.content,true)

document.importNode 用于递归import子节点,第一个参数为需要import的节点,第二个参数为一个boolean值,表示是否需要递归import所有的子节点。

tpl.content

tpl是该方法传入进来的参数,它是一个需要显示的视图模板的DOM对象,该DOM对象有一个content 属性表示该template下所有的子节点。

整个方法可以理解为,将要显示的template里的子节点都复制一份拿出来,然后append到目标div容器里进行显示。

现在就初步的完成一个简单的SPA应用,基于template。

下面贴上该demo完整的源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <template id="zhangsanTpl">
        <h1>i'm ZhangSan</h1>
        <ul>
            <li>姓名:ZhangSan</li>
            <li>年龄:20</li>
            <li>性别: 男</li>
        </ul>
    </template>
    <template id="lisiTpl">
        <h1>i'm Lisi</h1>
        <ul>
            <li>姓名:Lisi</li>
            <li>年龄:18</li>
            <li>性别: 男</li>
        </ul>
    </template>
    <div class="target">
        <h1>Index</h1>
    </div>
    <div>
        <button onclick="targetZhangsan()">zhangsan</button>
        <button onclick="targetLisi()">lisi</button>
    </div>
    <script type="text/javascript">
        let targetDiv = document.querySelector(".target");
        //清除显示容器
        let cleanTargetDiv = () => targetDiv.innerHTML = "";
        //添加模板到容器
        let appendTpl = tpl => targetDiv.appendChild(document.importNode(tpl.content,true));
        //改变路由
        let updateUrl = url => {
            if(Object.prototype.toString.call(url) == '[object String]'){
                history.pushState({},url,`/${url}`);
            }
            else {
                throw new Error('url must be a string');
            }
        }
        //初始化路由
        updateUrl("host");

        //zhangsan点击回调
        let targetZhangsan = () => {
            cleanTargetDiv();
            updateUrl('zhangsan');
            appendTpl(document.querySelector('#zhangsanTpl'));
        }
        //lisi点击回调
        let targetLisi = () => {
            cleanTargetDiv();
            updateUrl('lisi');
            appendTpl(document.querySelector('#lisiTpl'));
        }

    </script>
</body>
</html>

如果有错误或者有疑问请在下面回复给我吧,我会及时的改正或答复。

这里附上本文的源码。在我的GitHub 上,欢迎大家学习下载
https://github.com/HaoDaWang/SPA-for-Template

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值