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

来源:互联网 发布:淘宝贷款15万骗局 编辑:程序博客网 时间:2024/06/07 09:44

看到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