第8章 使用前端框架

8.1 使用Bootstrap


Bootstrap是一个简单易用,功能丰富的前端框架,主要提供了预定义的样式,元素布局,组件,图标等功能。Bootstrap可以通过给HTML元素添加class属性来方便地赋予元素预先定义好的样式和布局方式。

  • 8.1.1 Bootstrap概述
  • 8.1.2 布局
  • 8.1.3 导航栏
  • 8.1.4 其他常用组件

8.1.1 Bootstrap概述


Bootstrap目前常用的版本是v3、v4和v5。本书将以最新的v5版本为例介绍其使用方法。Bootstrap支持使用npm安装,也可直接通过script标签及link标签引入。为了配置简便突出要介绍的重点,这里使用后者。

下面是一段未使用框架的代码,定义了标题、三个按钮、一个无序列表和一个三行三列的表格。


<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>使用Bootstrap</title>
	</head>
    <body>
        <div>
            <h1>标题
            <button>按钮1</button><button>按钮2</button><button>按钮3</button>
            <input>
            <ul>
                <li>第一</li>
                <li>第二</li>
                <li>第三</li>
            </ul>
            <table>
                <thead><tr><th>#</th><th>1</th><th>2</th><th>3</th></tr></thead>
                <tbody>
                    <tr><td>1</td><td>一</td><td>二</td><td>三</td></tr>
                    <tr><td>2</td><td>甲</td><td>乙</td><td>丙</td></tr>
                </tbody>
            </table>
			</div>
		</body>
</html>
					

在浏览器打开这段代码看到的效果如下图所示。

要使用Bootstrap,需要引入其代码,Bootstrap的代码分为css代码和JavaScript。下面的代码通过link标签引入了Bootstrap v5.2.3版本的CSS代码。这个link标签应该放置在HTML5文档的开头部分,通常可以放在<head>标签中。


<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
						

下面的代码引入了Bootstrap v5.2.3版本的JavaScript代码。这个标签应该放到文档的结尾部分,通常可放到<body>标签之后。


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
						

引入Bootstrap代码文件后。给需要应用样式的标签添加class属性即可把预定设的样式应用到指定元素上。

首先给最外层的div标签添加class="container"属性,这是Bootstrap布局系统中放置在最外层容纳其他元素的类,这里仅简单使用,下一小节将详细介绍布局系统。这是代码如下所示。


<div class="container"></div>
						

对于按钮添加btn类可以应用基本按钮样式,但没有颜色也没有边框,对按钮通常要使用两个或以上的类。比如把上面代码中的button标签做如下修改。


<button class="btn btn-dark">按钮1</button>
<button class="btn btn-primary">按钮2</button>
<button class="btn btn-outline-primary btn-lg">按钮3</button>
						

按钮1被设为“深色”或者叫“暗色”样式,按钮2被设置为“主要操作”的按钮样式,按钮3被设置为“带轮廓线的主要操作”样式,尺寸是大号。“btn-sm”和“btn-lg”可以设置按钮的尺寸,前者是小号,后者是大号,二者都不使用则介于它们之间。

对于列表可通过下面的代码应用Bootstrap的样式。对于ul使用list-group,ul下的li标签使用list-group-item类。


<ul class="list-group">
	<li class="list-group-item">第一</li>
	<li class="list-group-item">第二</li>
	<li class="list-group-item">第三</li>
</ul>
						

对于表格,可以只给table标签添加class,“table”可以应用Bootstrap默认的样式,“table-success”增加了颜色,“table-striped”则使表格相邻的两行颜色深浅交错,便于区分,添加class后的代码如下所示。


<table class="table table-success table-striped"></table>
						

最终经过修改的html文档在浏览器中打开的效果如下图所示。

使用Bootstrap并给标签添加相应class属性后,看到的变化是标题,按钮,列表和表格中的字体发生变化,按钮边角更圆润,按钮之间增加了间距,列表和表格宽度变得更宽,占据更多空间。除此之外,改变浏览器窗口大小时,这些元素会自动调整大小和位置。点击查看代码

8.1.2 布局


Bootstrap一般使用使用带有container类的元素容纳页面内容,如上一小节例子中的<div class="container"></div>。container类可以决定其中内容的宽度,“container-fluid”总会占满100%的屏幕宽度,“container-sm”仅在小尺寸的屏幕(默认是小于576px)时会占满屏幕宽度,当屏幕尺寸变宽时将把内容居中显示,两侧会根据设置留出空白。

此外还有“container-md”,“container-lg”,“container-xl”,“container-xxl”它们依次可以充满更大尺寸的屏幕。“container”的设定与“container-sm”相同。它们具体的设置可以参考Bootstrap在线文档。

ontainer可以用于构建响应式前端。响应式前端指使用一个页面,一份代码适应不同尺寸屏幕的设备。但如手机这样的移动设备屏幕一般是高度大于宽度,所以在手机这样的“窄屏幕”上页面最好能占据100%的屏幕宽度,便于利用屏幕空间。

而在使用宽屏的计算机或电机机等设备,屏幕一般是宽度大于高度。所以页面可能会在左右两侧留出空白,实际的内容则居中显示。

上述各种尺寸的container就是用于决定具体从多么宽的屏幕开始在页面两边留出空白。

container之内,Bootstrap使用栅格系统实现元素定位和布局。“row”类会生成一个“行”,一行之内可以使用“col”定义多个列。例如下面代码。


<div class="row">
	<div class="col">这是一个很长的内容</div>
	<div class="col">少量内容</div>
	<div class="col"></div>
</div>
						

如下图所示,在开发者工具中选中这个元素后,可以看到row下的三个col被设置成了相同的宽度。

col类还支持设定宽度比例。如下面代码row下的两个div一个被设置为col-2另一个被设置为col-8。


<div class="row">
	<div class="col-2">col-2</div>
	<div class="col-8">col-8</div>
</div>
						

它们将分别占据20%和80%的宽度。

8.1.3 导航栏


导航栏是网站中常用的组件,通常出现在页面最顶端,用于显示网站主要功能或栏目,有时还提供搜索功能。下面的代码就是使用Bootstrap实现的基本的导航栏。使用HTML新增的nav标签,并设置了Bootstrap定义的“navbar”,“navbar-dark”,“bg-primary”和“navbar-expand”类。其中“navbar-expand”是导航栏中的元素不被折叠。


<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8">
        <title>使用导航栏</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
 rel="stylesheet">
    </head>
    <body>
        <nav class="navbar navbar-dark bg-primary navbar-expand">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">我的网站</a>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <a class="nav-link active" aria-current="page" href="#">
                            主页
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">链接</a>
                    </li>
                </ul>
                <form class="d-flex" role="search">
                    <input class="form-control mb" type="search" placeholder="输入关键词" aria-label="Search">
                    <button class="btn btn-success text-nowrap" type="submit">搜索</button>
                </form>
                </div>
            </div>
        </nav>
        <h1>标题</h1>
        <p>文章内容</p>
	</body>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
    </html>
						

<ul class="navbar-nav me-auto mb-2 mb-lg-0">标签中的每一个<li>是导航栏上的一个链接。<form class="d-flex" role="search">则生成了一个靠左侧的表单作为导航栏上的搜索框。上面代码在浏览器中展示的结果如下图所示。

为了在导航栏上展现更多链接和把链接分类,可以使用下拉菜单。可在上述代码的<ul>标签最后再增加一个<li>元素。使用class="nav-item dropdown"属性。


<li class="nav-item dropdown">
	<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
			菜单
	</a>
	<ul class="dropdown-menu">
		<li><a class="dropdown-item" href="#">项目1</a></li>
		<li><a class="dropdown-item" href="#">项目2</a></li>
		<li><a class="dropdown-item" href="#">项目3</a></li>
		<li><hr class="dropdown-divider"></li>
		<li><a class="dropdown-item" href="#">项目4</a></li>
	</ul>
</li>
						

这个<li>标签中<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown">是下拉菜单的名称,点击这个元素会触发菜单展开。展开后显示列表<ul class="dropdown-menu">中的内容。

带下拉菜单的导航栏如下图所示。

8.1.4 其他常用组件


Bootstrap提供了进度条组件,通过修改属性可动态控制进度条的完成比例。下面代码创建了4个不同样式的进度条,完成比例分别是25%,50%,75%和100%。


<br>
<div class="progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">
    <div class="progress-bar" style="width: 25%">25%</div>
</div>
<br>
<div class="progress" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
    <div class="progress-bar progress-bar-striped bg-info" style="width: 50%">50%</div>
</div>
<br>
<div class="progress" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
    <div class="progress-bar" style="width: 75%"></div>
</div>
<br>
<div class="progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="height: 20px">
    <div class="progress-bar" style="width: 100%"></div>
</div>
						

前两个进度条上有数字显示,第二个进度条有斜条纹,第三个进度条高度是1px,第四个进度条高度为20px。显示效果如下图所示。

下面代码展示了Bootstrap的页码导航组件。


<ul class="pagination">
	<li class="page-item"><a class="page-link" href="#">上一页</a></li>
	<li class="page-item"><a class="page-link" href="#">1</a></li>
	<li class="page-item"><a class="page-link" href="#">2</a></li>
	<li class="page-item"><a class="page-link" href="#">3</a></li>
	<li class="page-item"><a class="page-link" href="#">4</a></li>
	<li class="page-item"><a class="page-link" href="#">下一页</a></li>
</ul>
						

显示效果如下图所示。

8.2 使用ECharts


ECharts是基于JavaScript的可视化图表库,内置丰富的图表种类,包括折线图,柱状图,饼图,散点图,地图,关系图等。其官网有丰富的示例可供参考。

  • 8.2.1 ECharts概述
  • 8.2.2 常见二维图表
  • 8.2.3 饼图
  • 8.2.4 关系图
  • 8.2.5 地图数据展示

8.2.1 ECharts概述


为了配置的简便,对于Echarts,我们也直接通过<script>标签引入而不再使用npm安装。下面代码使用jsdeliver.com提供的cdn服务器引入了5.4.2版本的ECharts。如果需要寻找其他版本可浏览官方网站。


<!DOCTYPE html>
<html>
  <head>
<meta charset="utf-8" />
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.2/dist/echarts.min.js"></script>
  </head>
  <body>
<div id="main" style="width: 800px;height:400px;"></div>
  </body>
</html>
						

下面的代码创建了一个柱状图。


<script type="text/javascript">
// 在前面HTML代码中定义的div元素上初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 配置图表
var option = {
	title: {
	// 图表标题
	text: '鲁迅先生说过的话APP每月访问统计'
	},
	tooltip: {},
	legend: {
	data: ['IP数量', '访问量'] // 图例
	},
	xAxis: {  // X轴数据
	data: ["2021-05", "2021-06", "2021-07", "2021-08", "2021-09", "2021-10", "2021-11", "2021-12", "2022-01", "2022-02", "2022-03", "2022-04", "2022-05", "2022-06", "2022-07", "2022-11", "2022-12", "2023-01", "2023-02", "2023-03"]
	},
	yAxis: {},
	series: [
	{ // Y 轴数据 1,每月访问的IP数量
		name: 'IP数量',
		type: 'bar',
		data: [238, 492, 361, 299, 257, 374, 209, 185, 165, 103, 98, 102, 75, 100, 63, 3, 85, 72, 78, 75]
	},
	{ // Y 轴数据 2,每月访问量
		name: '访问量',
		type: 'bar',
		data: [1962, 3299, 2366, 2146, 1798, 3115, 1810, 2405, 1330, 486, 407, 490, 368, 576, 369, 13, 968, 355, 686, 578]
	}
	]
};
// 显示图表
myChart.setOption(option);
</script>
						

下图展示了上述代码绘制的柱形图。图的横坐标是月份,纵坐标是数量,两种颜色分别表示访客IP数量和访问量。因为同一个IP每月可能访问多次,所以访问量总是大于IP数量。

8.2.2 常见二维图表


最常用的图表是柱形图和折线图。上一小节已经给出了柱状图的示例。在上一节图表的基础上,把series中的数据的type从“bar”改为“line”即可把柱状图更改为折线图。下图展示了把在上面一个例子基础上,把访问量数据换成柱形图的效果。

这个图把两组数据放到一张图表中展示,但两组数据的范围差距略大,折线图的最大值超过3000,而柱形图最大值只有500,这种情况可以使用多坐标轴对数据展示。修改原配置中“yAxis: {}”添加两个坐标轴。


yAxis: [
    {
      type: 'value',
      scale: true,
      name: 'IP数',
    },
    {
      type: 'value',
      scale: true,
      name: '访问量',
  }
]
						

然后给series中两组数据分别添加yAxisIndex属性。对于IP数量,设置为“yAxisIndex: 0”,对于访问量设置为“yAxisIndex: 1”。经过这一修改,柱状图也能较好的展示了,修改后的效果如下图所示。

还可以绘制散点图,如下面代码描述了每月访问IP数量和每月总访问量的关系。


<!DOCTYPE html>
<html>
  <head>
  <meta charset="utf-8" />
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.2/dist/echarts.min.js"></script>
  </head>
  <body>
  <div id="main" style="width: 400px;height:400px;"></div>
  </body>
  <script type="text/javascript">
      // 在前面HTML代码中定义的div元素上初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));
      // 配置图表
      var option = {
        title: {
          // 图表标题
          text: '每月IP数和访问量关系'
        },
        xAxis: {  // X轴名称
          name: 'IP数'
        },
        yAxis: { // Y轴名称
          name: '访问量'
        },
        series: [
          {
            symbolSize: 15,
            data: [[85, 968], [72, 355], [78, 686], [75, 578], [3, 13], [103, 486], [98, 407], [361, 2366], [299, 2146], [185, 2405], [257, 1798], [492, 3299], [100, 576], [374, 3115], [209, 1810], [165, 1330], [238, 1962], [102, 490], [75, 368], [63, 369]],
            type: 'scatter'
          }
        ]
      };
      // 显示图表
      myChart.setOption(option);
	  </script>
</html>
						

下图展示了每月IP数量和访问量的散点图。代码中的symbolSize用来控制图中点的大小。

8.2.3 饼图


Echars提供了多种饼图样式。下面的代码创建了一个饼图。


// 在前面HTML代码中定义的div元素上初始化echarts实例
let myChart = echarts.init(document.getElementById('main'));
// 配置图表
let option = {
	title: {
		text: '鲁迅先生说过的话APP海外访客IP',
		subtext: '国家占比',
		left: 'center'
	},
	tooltip: {
		trigger: 'item'
	},
	legend: {
		orient: 'vertical', left: 'left'
	},
	series: [
		{
		name: '访问来自',
		type: 'pie',
		radius: '50%',
		data: [
			{ value: 8, name: '日本' }, { value: 1, name: '俄罗斯' }, { value: 21, name: '印度' },
			{ value: 2, name: '阿根廷' }, { value: 19, name: '美国' }, { value: 1, name: '捷克' },
			{ value: 2, name: '英国' }, { value: 1, name: '缅甸' }, { value: 10, name: '马来西亚' },
			{ value: 1, name: '印度尼西亚' }, { value: 6, name: '法国' },
			{ value: 1, name: '意大利' }, { value: 23, name: '新加坡' },
		],
		emphasis: {
			itemStyle: {
			shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)'
			}
		}
		}
	]
};
// 显示图表
myChart.setOption(option);
						

下图展示了上述代码绘制的饼图,图中效果是鼠标移动到饼图上,显示出具体的数据。option中的legend设定了图例的位置。radius参数设定了饼图的大小,支持相对大小和绝对大小。

下图展示了使用Echars绘制的南丁格尔玫瑰图和环形图。

8.2.4 关系图


ECharts提供多种关系图的布局,其中力引导布局方式支持自动计算点和边的位置,只需要提供点的信息和点间连线的信息,ECharts可以自动绘制关系图。下面的代码绘制了鲁迅先生说过的话APP中某段事件内,部分文章的点击量。


<head>
	<meta charset='utf8' />
</head>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.2/dist/echarts.min.js"></script> <script> // 把 main 区域设置成接近铺满窗口的大小 let width = window.innerWidth; let height = window.innerHeight; main.style.height = height * 0.95 + "px"; main.style.width = width* 0.95 + "px"; // 初始化Echarts 关系图 let chartDom = document.getElementById('main'); let myChart = echarts.init(chartDom); let option; // 显示加载中提示界面 myChart.showLoading(); option = { tooltip: {}, legend: [ { data: ['文集', '文章'] }], series: [ { name: '点击量', type: 'graph', layout: 'force', // 数据较多,这里仅保留部分,全部数据请参考随书源码 data: [{"id": 0, "name": "鲁迅全集", "symbolSize": 1, "value": 2135, "category": 0}, {"id": 1, "name": "朝花夕拾", "symbolSize": 19.5, "value": 590, "category": 0}, {"id": 2, "name": "无常", "symbolSize": 3.7, "value": 37, "category": 1}], links: [{"source": 0, "target": 1}, {"source": 1, "target": 2}], categories: [ { "name": "文集" }, { "name": "文章" } ], roam: true, force: { repulsion: 80 }, label: { show: true, position: 'right', formatter: '{b}' }, labelLayout: { hideOverlap: true }, scaleLimit: { min: 0.4, max: 2 }, lineStyle: { color: 'source', curveness: 0.3 } } ] }; // 根据 option 中的配置显示关系图 myChart.setOption(option); // 隐藏正在加载的提示 myChart.hideLoading(); </script>

下图展示了代码中定义的力引导布局关系图的显示效果。鼠标指向某个点时,会显示出节点数据详细信息。

8.2.5 地图数据展示


对于地图上数据的展示,EChats提供了基于百度地图API的数据展示。如需使用这个功能,需要到百度地图API网站注册,并申请密钥。对于个人开发者和非商业用途的使用可以免费获得密钥。下面代码展示了地图数据展示所使用的代码的HTML部分。


<!DOCTYPE html>
<html>
  <head>
<meta charset="utf-8" />
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.2/dist/echarts.min.js"></script>
    <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=你的密钥"></script>
  <script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.4.2/dist/extension/bmap.min.js"></script>
  </head>
  <body>
  <div id="main" style="width: 1300px;height:900px;"></div>
  </body>
</html>
						

上述代码中的“你的密钥”需要被替换成在百度地图API网站申请到的密钥。所以这个代码直接打开并不能正常使用。如需查看效果可访问本书网站链接中的在线示例。

下面代码则是JavaScript和数据。


<script type="text/javascript">
	// 在前面HTML代码中定义的div元素上初始化echarts实例
	var myChart = echarts.init(document.getElementById('main'));
	// IP数量数据,数据太多,这里有删减,全部数据请见随书代码
	let data = [{"name": "四川省", "value": 253} , {"name": "北京", "value": 221}, {"name": "广东省", "value": 388}];
	// 用到的省市在地图上的坐标(这个数据不一定准确,这里仅做示例,请勿再其他场景下使用)
	const geoCoordMap = {"四川省": [104.06, 30.67], "北京": [116.46, 39.92], "广东省": [113.23, 23.16]}
	// 把data中的数据转换成用于展示的数据
	const convertData = function (data) {
		var res = [];
		for (var i = 0; i < data.length; i++) {
			var geoCoord = geoCoordMap[data[i].name];
			if (geoCoord) {
			res.push({ name: data[i].name,  value: geoCoord.concat(data[i].value) });
			}
		}
		return res;
	};
	var option = {
		title: {
			text: '鲁迅先生说过的话APP国内用户IP分布 - 百度地图',
			subtext: '数据来自鲁迅先生说过的话APP', sublink: 'http://luxunquotation.codeplot.top/',
			left: 'center'
		},
		tooltip: { trigger: 'item' },
		bmap: { center: [104.114129, 37.550339], zoom: 5, roam: true,
			mapStyle: {
			styleJson: [// 地图风格配置,后面代码被删减,完整版请查阅随书代码
				{ featureType: 'water', elementType: 'all', stylers: { color: '#d1d1d1' } },
				{ featureType: 'land', elementType: 'all', stylers: { color: '#f3f3f3' }}
			]
			}
		},
		series: [
			{
			name: '访客数量',
			type: 'scatter', coordinateSystem: 'bmap', data: convertData(data),
symbolSize: function (val) { return val[2] / 10;}, encode: { value: 2 },
			label: { formatter: '{b}', position: 'right', show: false }, emphasis: { label: {show: true} }
			},
			{ name: 'Top 5', type: 'effectScatter', coordinateSystem: 'bmap',
data: convertData( data.sort(function (a, b) { return b.value - a.value;}) .slice(0, 6)),
			symbolSize: function (val) { return val[2] / 10; }, encode: {value: 2},
			showEffectOn: 'render', rippleEffect: { brushType: 'stroke' },
			label: { formatter: '{b}', position: 'right', show: true },
			itemStyle: { shadowBlur: 10, shadowColor: '#333' },
			emphasis: { scale: true }, zlevel: 1
			}
		]
		};
	// 显示图表
	myChart.setOption(option);
</script>
						

可使用Echarts和百度地图API自行动手生成基于地图的数据可视化图。除了原始数据外,还可以根据原始数据构造了一组排名前五的数据,使用单独符号在地图上展示。

8.3 使用Vue


Vue的发音和单词view相同,是一种用于构建用户界面的JavaScript框架。Vue使用标准的HTML,CSS和JavaScript。

  • 8.3.1 Vue概述
  • 8.3.2 Vite简介
  • 8.3.3 组件
  • 8.3.4 输出内容
  • 8.3.5 属性和事件绑定
  • 8.3.6 条件渲染和列表渲染
  • 8.3.7 表单输入绑定

8.3.1 Vue概述


安装Node.js后,通过执行下面的命令创建一个空的vue项目。


npm init vue
						

该命令将使用npm下载并执行create-vue,一个vue官方提供的用于初始化新vue项目的工具。create-vue会要求输入项目名称,并询问一系列项目配置的问题,可以直接用回车选择默认选项。使用create-vue创建的vue项目采用vite做打包。

项目创建完成后,package.json中的依赖有Vue和vite。但依赖还没有被下载,进入项目目录中执行npm istall下载和安装项目依赖。依赖安装完成后,可以直接执行npm run dev启动开发服务器程序,并可通过浏览器预览当前的开发的界面。

README.md文件是项目的说明文件,使用markdown语法。.gitignore文件是git版本管理工具关于忽略的文件的配置。index.html中的内容如下。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
						

src目录下的main.js是源码的入口文件,App.vue则是Vue的App源码,它代表了一个Vue组件, 如果使用Visual Studio Code编辑器,可以安装Vue的插件便于编辑.vue后缀的文件。其中main.js的内容入下。


import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
createApp(App).mount('#app')
						

引入App.vue组件和main.css文件。并在index.html中的id为app的div元素上挂载这个Vue的App。Vue中使用createApp创建App实例,并使用其mount方法和DOM元素关联。

App.vue中除了style标签外的内容如下。


						<script setup>
						import HelloWorld from './components/HelloWorld.vue'
						import TheWelcome from './components/TheWelcome.vue'
						</script>
								
						<template>
							<header>
								<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
								
								    <div class="wrapper">
								      <HelloWorld msg="You did it!" />
								    </div>
							  </header>
								
								<main>
								  <TheWelcome />
						    </main>
					 </template>
						

在vue文件中出现了Vue自定义的标签,例如它并不是HTML标签,在vue中有特定含义,8.3.3小节将给出进一步的介绍。

8.3.2 Vite简介


项目根目录下的vite.config.js文件时vite的配置,其中内容如下。


import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
						

Vite项目的index.html文件在项目根目录,而不像webpack在dist目录。Vite在构建时将以index.html文件为入口。在执行npm run dev会启动vite开发服务器,同样也是以根目录的index.htm为入口。

执行npm run build可通过Vite构建项目,构建后的代码在dist中,默认就是production版本。

使用默认配置时Vite构建的代码默认只支持较新版本的浏览器。但也可通过配置修改最低支持的浏览器标准。通常不需要修改这一配置,详细用法可参考Vite文档。

8.3.3 组件


Vue中组件有两种定义方式。8.3.1小节通过create-vue创建的项目中看到的是单文件组件。组件中大致包含视图和代码两部分。前面所述App.vue中的<template>标签中的内容就是对视图的定义。它们将被渲染到index.html中的对应的div标签(因为main.js中绑定了这个div标签)。

create-vue创建的项目包含了多个自定义组件,比较繁琐,为了简便,先删除“src/components”目录, “src/ assets/ base.css”文件和 “src/ assets/ logo.svg”文件。清空“src/App.vue”和“src/ assets/ main.css”中的内容。

在App.vue中写入下面内容。


<script>
export default {
	data() {
	return {
		count: 0 // 这个count将被渲染到下面 {{ count }} 的位置
	}
	}
}
</script>

<template>
	<h1>Hello Vue</h1>
	<p v-on:click="count++">点击次数: {{ count }}</p>
</template>
						

通过npm run dev启动开发服务器(如果开发服务器已经在运行中则不需要),通过输出的地址访问页面,将看到如下图所示的页面。

可以看到<templete>标签中的标签作为HTML内容被放到了页面上。<script>标签中的data()里的count变量被渲染到了

标签的内容中。v-on:click也是Vue定义的标签属性,意为给<p>标签的点击事件绑定了处理函数,在这里是只有一个语句的匿名处理函数“count++”,这里访问到的count同样是<script>定义的。故点击<p>标签后可观察到显示的点击次数增加。

8.3.4 输出内容


前一小节使用的“{{ count }}”被称为文本插值,用于把一个JavaScript表达式作为文本内容插入到HTML视图中。如果JavaScript中对应变量的值发生改变,DOM上展示的内容会自动变化,Vue会负责处理这一更新的过程。这类似于本书之前介绍过的修改元素innerText属性来更新DOM内容的所做的事情。

但Vue负担了更新DOM的工作,我们的JavaScript程序则只需要关心维护变量的值。

如果要把JavaScript表达式作为HTML内容插入,可以使用标签的v-html属性。如下面语句把表达式作为HTML内容插入到

标签内,如同修改p元素的innerHTML属性。


								<p v-html="`点击次数:<span> ${count}</span>`"></p>
						   

8.3.5 属性和事件绑定


要动态设定HTML元素的属性则可使用属性绑定。如下面的代码把div的id设置成变量div_id的值。


							<div v-bind:id="div_id"></div>
					   

这一属性可简写为“:id”。

前一小节使用到的v-on:click用于绑定元素的事件,v-on:可以用“@”代替,如“@click”可绑定点击事件。

绑定的事件处理函数除了使用匿名函数外,更常见的用法是引用methods中定义的方法。如下面的代码。


						  <script>
						  export default {
							  data() {
							    return {
							      count: 0,
							      todo: [{id: 1, name: "购物"}, {id: 2, name: "运动"}]
							    }
							  },
							  methods: {
							    hello(event) {
							      console.log(event);
							      console.log(this);
							      console.log(this.todo);
							      alert("hello");
							    }
							  }
							}
							</script>
							<template>
						     <h1>Hello Vue</h1>
							  <p @click="count++">点击次数: {{ count }}</p>
							  <button @click="hello">Hello</button>
							  <ul>
							    <li v-for="item in todo">{{ item.id }} - {{ item.name }}</li>
							  </ul>
							</template>
				       

点击按钮后会调用hello方法。这里的event参数是默认传入的事件对象,与第7章中介绍的一致,但这里事件处理方法中的this变量却不再代表正在发生该事件的元素,而表示了当前的Vue组件对象。

8.3.6 条件渲染和列表渲染


v-if属性用于根据条件渲染,如果一个元素带有v-if属性,那么只有在这个属性中的JavaScript表达式的值为真时,这个元素才会显示。还有对应的v-else,和v-else-if。与v-if类似的还有v-show,区别是v-if的条件为false时,这个元素就不会被渲染,但这种情况下v-show会渲染元素,但不显示,即元素被隐藏。

v-for执行用于渲染JavaScript的列表。如有下面的变量。


							todo: [{id: 1, name: "购物"}, {id: 2, name: "运动"}]
						   

可通过如下方式渲染一个列表。


							<ul>
								<li v-for="item in todo">{{ item.id }} - {{ item.name }}</li>
							</ul>
						   

这段代码的显示效果和渲染出的结果如下图所示。如果对应变量的内容发生变化时,该列表也会动态更新。

8.3.7 表单输入绑定


除了提供从JavaScript表达式到视图上内容的绑定,Vue还支持把表单输入的值绑定到JavaScript变量。这样JavaScript程序不必再通过类似input元素的value属性获取表单元素输入值了。下面的代码给出了input标签的绑定方法。


<script>
export default {
		data() {
	return {
		message: ""
		}
		},
		methods: {
		}
	}
</script>
	
<template>
	<p>输入的信息是: {{ message }}</p>
	<input v-model="message" placeholder="请输入" />
</template>
						

<input>标签的输入内容将存入message变量,同时再通过文本内容绑定输出到<p>标签中。

对于radio类型的<input>标签,多个标签可绑定到同一个变量,变量的值就是选定的<input>标签的value。而checkbox还支持绑定到一个列表变量,被选中的value都会出现在列表中。

v-model也同样适用于<textarea>元素和<select>对象

8.4 小结


本章介绍了几种常见的前端开发框架。使用框架开发可以大大提高开发效率,并把精力集中在业务逻辑上。对于框架使用的很多细节这里都没有做详细的介绍,而且这些框架仍然在持续更新中,随时可能出现新的功能,在使用这些框架开发时,还请参考它们官方发布的在线文档。

8.5 课堂练习:HTML5英汉词典


本节将使用Vue.js开发一个支持英汉和汉英查询的词典。

  • 8.5.1 准备数据
  • 8.5.2 创建项目
  • 8.5.3 获取词典数据
  • 8.5.4 实现查询逻辑
  • 8.5.5 查询中文和英文单词模糊匹配

8.5.1 准备数据


词典使用的数据来自开源项目https://github.com/skywind3000/ECDICT,但经过删减和处理,因为原词典体积太大。处理后的词典文件为ecdict.small.json。

词典文件格式是json,是一个包含14,048个单词的列表。其中每个单词是一个长度为3的列表,其内容是“单词”,“音标”和“中文释义”。下面给出了几个单词的示例。


 [['a', 'ei', '第一个字母 A; 一个; 第一的\\r\\nart. [计] 累加器, 加法器, 地址, 振幅, 模拟, 区域, 面积, 汇编, 组件, 异步'],
 ['aback', "ә'bæk", 'adv. 向后, 朝后, 突然, 船顶风地'],
 ['abandon', "ә'bændәn", 'vt. 放弃, 抛弃, 遗弃, 使屈从, 沉溺, 放纵\\nn. 放任, 无拘束, 狂热']]
						

8.5.2 创建项目


使用npm init vue命令,项目名称为h5dict,其他选项全部采用默认配置。然后输入下面命令进入项目目录并安装依赖。


							cd h5dict
							npm install
						

删除“src/components”目录, “src/ assets/ base.css”文件和 “src/ assets/ logo.svg”文件。清空“src/App.vue”和“src/ assets/ main.css”中的内容。在“src/App.vue”中写入下面内容。


							<script>
								export default {
								  data() {
								    return {
								      key: ""
								    }
								  },
								  methods: {
								  }
								}
								</script>
								
								<template>
								  <h1>英汉汉英词典</h1>
								  <input v-model="key" placeholder="请输入要查询的内容" />
								  <p>{{ key }}</p>
								</template>
						

使用命令启动开发服务器。然后根据输出的服务器地址,从浏览器打开测试页面,当前页面效果如下图所示,此时仅实现了把输入的单词显示到下方。

8.5.3 获取词典数据


在“export default {”后添加如下代码。


mounted() {
	fetch("ecdict.small.json").then(response => {
		return response.json();
	}).then(response => {
		this.dict = response;
	})
}
						

mounted方法将在组件初始渲染并创建DOM后运行,可以用来执行初始化组件的代码,比如在这里用于下载数据文件。

这里直接把下载完成的数据存到dict变量中。应该在data()中也增加dict变量。


data() {
	return {
		key: "",
		dict: [],
	}
},
						

8.5.4 实现查询逻辑


可使用<input>标签的@change属性绑定查找单词的处理函数。Vue还支持侦听器的功能,可以让我们在Vue组件内某个变量被修改时执行操作。实现代码如下。


<script>
	export default {
		mounted() {
			fetch("ecdict.small.json").then(response => {
				return response.json();
			}).then(response => {
				this.dict = response;
			})
		},
		data() {
			return {
				key: "",
				dict: [],
				word: "",
				phonetic: "",
				chinese: "",
			}
		},
		watch: {
			key(newKey,oldKey) {
				for (let d of this.dict) {
					if (d[0] == newKey) {
						this.word = d[0];
						this.phonetic = d[1];
						this.chinese = d[2];
					}
				}
			}
		}
	}
</script>
	
<template>
	<h1>英汉汉英词典</h1>
	<input v-model="key" placeholder="请输入要查询的内容"/>
	<p>{{ word }}</p>
	<p>[{{ phonetic }}]</p>
	<p>{{ chinese }}</p>
</template>
						

通过在watch中添加“key(newKey,oldKey) {”实现了在key变化时执行特定操作。这里的查词操作是遍历整个词典,如果发现词典里的词和用户输入的key一致时,把词典里的结果输出到输入框下方的三个<p>标签中。实现的结果如下图所示。

8.5.5 查询中文和英文单词模糊匹配


首先需要一个变量检查是否匹配到准确的词汇。仅当未匹配到准确词汇时才到中文释义中寻匹配关键词,借此实现中文模糊匹配。

对于英文模糊匹配这里实现前缀匹配,即输入关键词“a”,则可以匹配到所有以“a”开头的关键字。另外,增加忽略大小写的功能,因为词库里的词都是小写,简单起见,每次查询直接把key转换成小写。

模糊匹配到的词需要用列表形式展示出来,所以定义新的变量candidates如下用于保存要展示的模糊匹配到的词语


							candidates: []
					    

在</template>前增加下面的代码,用于显示candidates列表。


<hr>
<div>
	<div v-for="item in candidates" @click="select(item[0])" style="border-style: solid; border-color: black;">
		<p >{{ item[0] }}</p>
		<p >{{ item[1] }}</p>
	</div>
</div>
					    

为了实现点击备选单词就跳转到备选单词的功能,要给备选单词添加点击事件,调用select方法。下面是select的实现。


select(k) {
	this.key = k;
}
					    

实现模糊查询把对key的侦听器修改为如下代码。


watch: {
	key(newKey,oldKey) {
		newKey = newKey.replace(/^\s*|\s*$/g,""); // 去除前后的空格
		if (!newKey)
			return;
		newKey = newKey.toLocaleLowerCase()
		this.candidates = [];
		let found = false;
		for (let d of this.dict) {
			if (d[0] == newKey) {
				this.word = d[0];
				this.phonetic = d[1];
				this.chinese = d[2];
				found = true;
			}
			else
				if (this.candidates.length < 100 && d[0].startsWith(newKey)) 
					this.candidates.push([d[0], d[2]])
		}
		if (!found) {
			for (let d of this.dict) {
				if (this.candidates.length > 300)
					break;
				if (d[2].includes(newKey))
					this.candidates.push([d[0], d[2]]);
			}
		}
	}
}
					    

前缀匹配英文单词时,当输入的字母较少时,可能一次匹配到很多单词,所以添加限制,只有在候选单词数量不超过100个时,才对单词做前缀匹配。

至此词典的基本功能已经实现。