webpack: 是前端项目工程化的具体解决方案
,其主要功能,是提供了友好的前端模块化开发支持
以及代码压缩混淆、处理浏览器端JavaScript的兼容性、性能优化
等强大的功能。 目前Vue,React等前端项目,基本都是基于webpack工程化开发
在这我们使用一个demo(创建列表隔行变色项目)来说明webpack的基本使用方法:
npm init -y
命令,初始化包管理配置文件package.json
src
源代码目录index.html
首页和src->index.js脚本文件npm install jquery -S
命令,安装jQuery。之前我们都是直接下载jQuery文件拷贝到项目,这里直接使用npm命令安装。注意: -S 是–save的缩写, 是告诉 npm把包安装到dependencies目录下
上述效果直接运行把如图的错误, 浏览器的兼容性错误。
webpack的安装: 我们可以在终端运行如下命令,安装webpack相关的两个包:npm install [email protected] [email protected] -D
注意:-D 是 --save-dev的简写 是告诉npm把该包安装在devDependencies目录下, 只在开发模式下使用
webpack.config.js
的webpack配置文件,并初始化如下配置// 使用Node.js中的到处语法,向外到处一个webpack的配置对象
module.exports = {
// mode用来指定构建模式, 可选值 development 和 production
// 开发时一定要用development,因为追求的是打包的速度,而不是体积
// 反过来,发布上线的时候一定能要用production,因为上线追求的是体积小,而不是打包速度
mode: 'development'
}
package.json
的scripts节点下,新增dev脚本如下
:"scripts": {
// 自定义脚本: dev(脚本名称) , npm run dev 执行名称为dev的脚本
"dev" : "webpack"
},
npm run dev
命令,启动webpack进行项目的打包构建运行npm run dev
报如下错误:
原因: node.js版本太高, 本人这里是v18.12.1
, 我这里解决是把node.js的版本降低到v16.19.1
注意: webpack.config.js配置文件是在npm run dev时,执行dev脚本文件之前,会读取这个配置文件的内容, 然后根据配置文件的内容来打包运行项目
scr -> index.js
dist -> mani.js
在webpack.config.js配置文件中,通过entry节点
指定打包的入口
。通过output节点
自定打包的出口
const path = require('path')
module.exports = {
// mode用来指定构建模式, 可选值 development 和 production
mode: 'development',
entry: path.join(__dirname, './src/index.js'),// 打包入口文件的路径
output: {
path: path.join(__dirname, './dist'), // 输出文件的存放路径
filename: 'bundle.js' // 输出文件的名称
}
}
webpack插件: 通过安装和配置第三方的插件,可以拓展webpack的能力
,从而让webpack用起来更方便。
最常用的webpack插件有如下两个:
webpack-dev-server
: 类似node.js阶段用到的nodemon
工具, 每当修改了源代码,webpack会自动进行项目的打包盒构建。安装命令:npm install [email protected] -D
package.json -> scripts
中的dev
命令如下"dev": "webpack serve"
npm run dev
http://localhost:8080/
地址,查看自动打包效果启动一个实时打包的http服务器
当我们配置好package.json文件之后,执行npm run dev
命令可能发生如下错误, 产生原因可能是没有安装webpack-cli库
或者是webpack-cli库的版本过低
导致
npm run dev
后):
http://localhost:8080/
上了dist
的文件下的bundle.js
文件, 直接输入在/
跟路径下面了,并且是隐藏文件,我们看不到的, 但是我们可以直接使用由上面结果可知webpack-dev-server
插件并没有把生成的bundle.js
文件放到物理磁盘
,而是直接放到内存中
html-webpack-plugin
: webpack中的html插件(类似一个模版引擎插件),可以通过此插件自定制index.html页面的内容
终端执行命令npm install [email protected] -D
当我们执行npm run dev
之后,点击http://localhost:8080/
之后不会出现,需要我们选择的页面,而是直接展示index.html
页面。
html-webpack-plugin
插件做的事情:
也被放到了内存中
页面
,自动注入了
打包的bundle.js文件我们可以在webpack.config.js配置文件中,可以通过devserver
节点对webpack-dev-server插件进行更多的配置,示例代码如下:
注意: 凡事修改了webpack.config.js配置文件,或修改了package.js配置文件,必须重启实时打包的服务器
,否则最新的配置文件无法生效
在实际开发过程中,webpack默认只能打包处理.js
后缀名结尾的模块。其它非.js后缀名结尾的模块
,webpack默认处理不了,需要调用loader加载器才可以正常打包
,否则会报错!
loader加载器的作用:协助webpack大包处理特定的文件模块
。比如:
// 导入样式(在webpack中一切皆模块,都可以ES6导入语法进行导入和使用)
// 如果某个模块中,使用from接收到的成员为undefined,则没有必要进行接收
import '../css/index.css'
当我们在index.js中导入.css文件时报如下错误:
安装处理.css文件的loader:
npm install [email protected] [email protected] -D
命令,安装处理css文件的loadermodule ->rules
数组中添加laoder规则如下: module.exports = {
module: { //所有第三方文件模块的匹配规则
rules: [ // 文件后缀名的匹配规则
{test: /\.css$/, use:['style-loader', 'css-loader']}
]
}
}
其中test
表示匹配的文件类型
,use
表示对应要调用的loader
注意: use数组中指定的loader顺序是固定
,多个loader的调用顺序是从后往前调用
- webpack默认只能打包处理.js结尾的文件,处理不了他后缀的文件
- 由于代码中包含了
index.css
这个文件,因此webpack默认处理不了- 当webpack发现某个文件处理不了时候,会查找
webpack.config.js
这个配置文件,看module.rule
数组中,是否匹配了对应的loader加载器- webpack是把index.css这个文件,先转交给最后一个loader进行处理(先转交给css-loader)
- 当css-loader处理完毕之后,会把处理的结果,转交给下一个loader(转交给style-loader)
- 当style-loader处理完毕之后,发现没有下一个loader了,于是把处理的结果,转交给webpack
- webpack把style-loader处理的结果,合并到
/dist/bundle.js
中,最终生成打包好的文件
npm install [email protected] [email protected] -D
命令module -> rules
数组中,添加loader规则如下:module: { //所有第三方文件模块的匹配规则
rules: [ // 文件后缀名的匹配规则
{test: /\.less$/, use:['style-loader', 'css-loader', 'less-loader']},
]
}
在HTML页面中,例如icon图标,可以转换成base64字符串,如果是直接图片路径下载的话,浏览器需要多发送一个请求。 但是如果我们直接使用base64字符串来加载的话,在你请求网页的时候,就会直接把把图片解析展示出来,而不是请求完网页,在发送一次图片的请求。
如果你的icon图片很多,那么可以省略很多次图片的请求, 这也是网页性能优化的一个点。但是base64字符串方式有个缺点,就是图片转换成base64字符串之后,体积会变大,所以大图片不是适合转换base64字符串。
icon小图片的优化,除了使用base64字符串的方式,还可以使用精灵图。
我们可以安装一个专门处理URL和文件的插件:
npm install [email protected] [email protected] -D
命令module -> rules
数组中,添加loader规则如下: module: { //所有第三方文件模块的匹配规则
rules: [ // 文件后缀名的匹配规则
// 如果需要调用loader只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组
{test: /\.jpg|png|gif/, use: ['url-loader?limit=2222']},
]
}
其中?
之后的是loader的参数项
:
图片的大小
,单位是字节(byte)<=limit
大小的图片,才会被转换成base64格式的图片webpack只能打包处理一部分
高级的JavaScript语法。对于那些webpack无法处理的高级js语法,需要借助与babel-loader
进行打包处理。例如webpack无法处理下面JavaScript代码:
$(function(){
$('li:odd').css('background-color','red')
$('li:even').css('background-color', 'green')
})
// 定义名为info的装饰器
function info(target) {
// 为目标添加静态属性 info
// 装饰给谁用, target就只像谁
target.info = 'Person info'
}
// 为Person类 应用info装饰器
@info
class Person {}
// 打印Person 的静态属性 info
console.log(Person.info);
装饰给谁用, target指向谁 这里 targer指向person
运行命令安装对应依赖包:npm install [email protected] @babel/[email protected] @babel/[email protected] -D
在webpack.config.js的module -> rules
数组中,添加到loader规则如下:
module: { //所有第三方文件模块的匹配规则
rules: [ // 文件后缀名的匹配规则
// 注意必须使用排除项,因为node_modules目录下的第三方包不需要被打包, 因为第三方中的js兼容性,不需要程序员关心
{test: /\.js$/, use: 'babel-loader', exclude: '/node_modules'},
]
}
在项目的根目录下,创建名为babel.config.js
的配置文件,定义Babel的配置项
如下:
module.exports = {
plugins: [
// 声明babel可用插件, 将来webpack在调用babel-loader的时候,会先加载plugins差价来使用
["@babel/plugin-proposal-decorators", { legacy: true }]
]
}
详情参考Babel的官网地址:babel-loader的官网地址
配置webpack的打包发布:
package.json
文件的scripts
节点下,新增build命令如下:"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack serve", // 开发环境中, 运行dev命令 在开发环境中加入serve指定是把打包的文件放到内存中,并没有放到物理磁盘中
"build": "webpack --mode production" // 项目发布时,运行build命令 。 不需要serve指定,项目发布中需要把文件打包在物理磁盘上。然后发送到后台发布
},
--mode
是一个参数项,用来指定webpack的运行模式
。production
代表生产环境,会对打包生成的文件进行代码压缩
和性能优化
。
注意: 通过 --mode 指定的参数项, 会覆盖
webpack.config.js中的model选项
当我们执行npm run build
指令后,生成的dist的结构目录如下:
我们会发现所有的文件全都在一个文件下,这样看起来会很混乱,我们可以在webpack.config.js配置文件中配置,把文件分类生成到不同的子目录下:
图片文件楚了上面配置方法,还可以这样配置{test: /\.jpg|png|gif/, use: 'url-loader?limit=2222&outputPath=image'},
配置文件后,再次执行命令npm run build
后生成的目录如下:
为了在每次打包发布时自动清理掉dist目录中的旧文件
,可以安装并配置clean-webpack-plugn
插件:
npm install [email protected] -D
命令// 按需导入,得到插件的构造函数之后,创建插件的实例对象
// 这种导入方式是结构赋值,得到的结果相当于解析了一层
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 创建
const cleanPugin = new CleanWebpackPlugin()
module.exports = {
// 把创建的cleanPugin 插件实例对象, 挂载plugin结点中
plugins: [cleanPugin],
}
什么是Source Map?
Source Map就是一个信息文件, 里面存储着位置的信息
。也就是说, Source Map文件中存储着压缩混淆后的代码,所对应的转换前的位置
有了它,出错的时候,除错工具将直接显示原始代码, 而不是转换后的代码
,能够极大的方便后期的调试。
默认Source Map的问题: 开发环境下默认生成的Source Map, 记录的是生成后的代码的位置
。会导致运行时报错的行数
与源代码的行数
不一致的问题。示意图如下:
开发环境下,推荐在webpack.config.js
中添加如下配置,即可保证运行时报错行数
与源代码的行数
保持一致:
module.exports = {
// eval-source-map 仅限在“开发模式”下使用,不建议在”生产模式“下使用
devtool:'eval-source-map',
}
webpack生产环境下的
Source Map: 在生产环境下,如果省略了devtool选项
,则最终生成的文件中不包含Source Map
。这能欧防止原始代码
通过Source Map的形式暴露
给别有所图之人:
注意: 在实际发布的时候,建议大家把devtool的值设置为nosources-source-map或直接关闭Source Map
在生产环境下,,如果想在定位报错行数的同时,展示具体报错的源码。
此时可以将devtool
的值设置为source-map
。实际效果如图所示:
eval-source-map
,好处:可以精准定位到具体的错误行关闭Source Map
或将devtool的值设置为nosources-source-map
, 好处:防止源码泄漏,提高网站的安全性在实际开发中需要自己配置webpack吗?
不需要
实际开发中会使用命令行工具(俗称CLI)一键生成
带有webpack的项目,所有的webpack配置项都是现成的,我们只需要知道哦啊webpack中的基本概念即可
最后我们在代码中可以指定@符号表示src这一层目录, 这样我们在导入文件的时候就不用使用../../
等这样的符号, 直接从src这一层目录往下找文件, 不是从里往外找:
module.exports =
resolve: {
alias: {
// 告诉webpakck, 程序员写的代码中, @符号表示src这一层目录
'@':path.join(__dirname, './src/')
}
}
}
vue是一套用于构建用户界面的前端框架。vue框架的特性,主要体现在如下两个方面:
数据驱动视图:
在使用vue的页面中,vue会监听数据的变化
,从而自动
重新渲染页面的结构。示意图如下:
好处: 当页面数据发生变化时,页面会自动重新渲染
注意: 数据驱动视图是单向的数据绑定
双向数据绑定: 在填写表单时,双向数据绑定可以辅助开发者
不操作DOM的前提下
,自动
把用户填写的内容同步到
数据源中
好处: 开发者不需要手动操作DOM元素,来获取表单元素最新的值
MVVM 是vue实现
数据驱动视图
和双向数据绑定
的核心原理。MVVM指的是Model、View和ViewModel,它把每个HTML页面都拆分成这三个部分。
在MVVM的概念中:
MVVM的工作原理: ViewModel作为MVVM的核心
,是它把当前页面的数据源(Model)和页面的结构(View)
连接在一起。
当数据源发生变化时
,会被ViewModel监听到,VM会根据最新的数据源自动更新
页面的结构
当表单元素的值发生变化
时,也会被VM监听到,VM会把变化过后最新的值自动同步
到Model数据源中
安装vue-devtoools的调试工具:vue官方提供的vue-devtools调试工具,能够方便开始者对vue项目进行调试与开发。
关于vue-devtools的安装过程这里不过多描述
配置Chrome浏览器中的vue-devtools:
点击Chrome浏览器上角的...
按钮,选择更多工具 -> 扩展程序 -> Vue.js devtools详细信息
,并勾选如下两个选项:
指令(Directives)
: 是vue为开发者提供的模版语法
,用于辅助开发者渲染页面的基本结构
。
vue中的指令按照不同的用途
可以分为如下六大类:
内容渲染指令
用来辅助开发者渲染DOM元素的文本内容
。常用的内容渲染指令有如下3个:
渲染值会覆盖标签中的默认值
插值表达式(Mustache)
v-text
和插值表达式
只能渲染纯文本内容
。如果要把包含HTML标签字符串
渲染为页面的HTML元素,则需要用到v-html这个指令属性绑定指令:如果需要元素的属性
动态绑定属性值
,则需要用到v-bind
属性绑定指令。示例代码如下:
注意: 插值表达式只能在元素中的内容节点中,不能用在元素的属性节点中
v:bind:
指令,为元素的属性动态绑定值,也可以简写为英文的:
v-bind:
属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包括单引号,例如:
2.4.3 事件绑定
vue提供了v-on事件绑定
指令,用来辅助程序员为DOM元素绑定事件监听。
注意: v-on:click 可以简写为 @click(推荐使用)
方法传参:绑定事件的同时可以在()内传参
- 如果没有传递参数, 在绑定事件方法,有默认的
事件对象event
作为参数
- 如果传递了参数, 传递的参数会覆盖默认的
事件对象event
,如果需要使用默认事件对象event
,那需要在多传递一个参数,使用$event
固定写法表示事件对象
注意: 原生DOM对象有onClick、oninput、onkeyup等原生事件,替换为vue的事件绑定形式后,分别为v-on:click、v-on:input、v-on:keyup
2.4.4 事件/按键 修饰符
事件修饰符:在事件处理函数中调用event.preventDefault() (组织默认点击跳转行为)
或event.stopPropagation() (阻止冒泡)
是非常常见的需求。因此vue提供了事件修饰符的
概念,来辅助程序员更方便的对事件的触发行为进行控制
。常用的5个修饰符如下:
注意:@click.prevent;既为a标签绑定了click事件,同时取消了a标签的默认跳转行为
按键修饰符: 在监听键盘事件
时,我们经常需要判断详细的按键。
此时,可以为键盘相关的事件
添加按键修饰符
2.4.5 双向绑定
vue提供了v-model双向数据绑定
指令,用来辅助开发者不操作DOM
的前提下,快去获取表单的数据
v-model
: 双向绑定,能和表单元素、下拉菜单元素组合使用, 但是和只展示的标签组合是没有意义的
v-bind
:单项绑定,当数据发生变化时,会通知页面;但是在页面修改数据,是不会同步修改数据源的
为了方便对用户输入的内容进行处理
,vue位v-model
指令提供了3个修饰符,分别是:
2.4.6 条件渲染指令
条件渲染指令
用来辅助开发者按需控制DOM的显示和隐藏
。条件渲染指令有如下两个分别是:
- v-if
- v-show
v-show
的原理是:动态为元素添加或移除display:none
样式,来实现元素的显示和隐藏,如果要频繁的切换元素的显示状态,用v-show性能会更好
v-if
的原理是:每次动态创建或移除元素,实现元素的显示和隐藏,如果刚进入页面的时候,某些元素默认不需要被展示,而后期这个元素很可能也不需要被展示出来,此时v-if性能更好
v-if指令还可以与v-else-if
和v-else
指令使用,相当于if {} else if{} esle {}
, v-else、v-else-if
必须与v-if
组合一起使用,不能单独使用,单独使用时不被识别
2.4.7 列表渲染指令
vue提供了v-for
列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。
v-for指令需要使用item in items
形式的特殊语法,其中:
- items 是待循环的数组
- item 是被循环的每一项
v-for指令还支持一个可选的
第二个参数,即当前项的索引
,语法格式:(item, index)in list
:
注意: v-for指令中的item项和index索引都是形参,可以更具需要进行重命名。 例如(user, i)in list
官方建议:只要使用到了v-for
指令,那么一定要绑定一个:key属性
, 而且这里建议尽量把id作为key的值,并且官方对key的值是由要求的:字符串或数字类型。 这里需要注意的是key的值是千万不能重复的,否则会终端报错 Dulicate keys detected
- key的注意事项:
- key的值只能是
字符串
或数字
类型
- key的值
必须具备唯一性
(即:key的值不能重复,唯一性指的是key和数据具有唯一绑定性)
- 建议把
数据项id属性的值
作为key的值(因为id属性的值具有唯一性)
- 使用
index的值
当做key的值没有任何意义
(因为index的值不具有唯一性)
- 建议使用v-for指令时
一定要指定key的值
(既能提升性能、又防止列表状态紊乱)
2.5 品牌列表案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>品牌列表案例</title>
<link rel="stylesheet" href="../../lib/bootstrap.css">
<link rel="stylesheet" href="./css/brandlist.css">
<script src="../../lib/vue-2.6.12.js"></script>
</head>
<body>
<div id="app">
<!-- 卡片区域 -->
<div class="card">
<div class="card-header">
添加品牌
</div>
<div class="card-body">
<!-- 添加品牌的表单区域 -->
<form @submit.prevent="add">
<div class="form-row align-items-center">
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">品牌名称</div>
</div>
<input type="text" class="form-control" placeholder="请输入品牌名称" v-model.trim="name">
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2">添加</button>
</div>
</div>
</form>
</div>
</div>
<!-- 表格区域 -->
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">品牌名称</th>
<th scope="col">状态</th>
<th scope="col">创建时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in list" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" :id="'customSwitch' + item.id" v-model="item.status">
<label class="custom-control-label" :for="'customSwitch' + item.id">{{ item.status ? '已启用' : '已禁用' }}</label>
</div>
</td>
<td>{{ item.time }}</td>
<td>
<a href="javascript:;" @click="remove(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
name:'',
list:[
{id: 1, name: '宝马', status: false, time: new Date()},
{id: 2, name: '奔驰', status: true, time: new Date()},
{id: 3, name: '奥迪', status: false, time: new Date()}
]
},
methods: {
remove(id) {
// 根据id来删除数据
// filter : 把不符合条件的元素过滤掉
this.list = this.list.filter( item => item.id != id)
},
add() {
if (this.name === '') return alert('请输入品牌名称')
// 如果不为空则 执行添加逻辑
const obj = {
id: this.list.length + 1,
name: this.name,
status: true,
time: new Date()
}
this.list.push(obj)
this.name = ''
}
}
})
</script>
</body>
</html>
2.6 过滤器
过滤器(Filters)是vue为开发者提供的功能,常用与文本的格式化
。过滤器可以用在两个地方:插值表达式、v-bind属性绑定
。过滤器应该被提那家在JavaScript表达式的尾部
,由管道符
进行调用.但是在Vue3.0中已经不支持过滤器了, 官方建议使用计算属性和方法来代替
- 过滤器的注意点:
- 要定义在filters节点下,本质上是一个函数
- 在过滤器函数中,一定要有return值
- 在过滤器的形参中,就可以获取到“管道符”前面待处理的那个值
私有过滤器和全局过滤器: 在filters节点下定义的过滤器,成为私有过滤
器,因为它只能在vm实例所控制的el区域内使用。
如果希望在多个vue实力之间共享过滤器
,则可以按照如下的格式定义全局过滤器:
// 全局过滤器, 独立于每个vm实例之外
// Vue.filter()方法接受两个参数: 第一个参数: 全局过滤器的名称 第二个参数:全局过滤器的处理函数
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1) + '~~~'
})
注意:如果全局过滤器函数和私有过滤器名字一致,此时按照就近原则
,调用的是私有过滤器
过滤器的本质
是JavaScript函数
,因此可以接受参数,格式如下:
2.7 侦听器
watch侦听器
允许开发者见识数据的变化,从而针对数据的变化做特定的操作
。
immediate
: 默认值false,其作用是控制侦听器是否自动触发一次
有时候我们会有需求,在界面初始化时,就自动触发触发一次监听器,监听属性的变化, 这时候我们可以做如下实现:
deep: 开启深度监听,只要对象中任何一个属性变化,都会触发“对象的监听器”, 有时候我们监听的是一个对象, 如果想要对象中的每个属性的变化都会引起对象的变化,那么使用deep关键字。但是只是想要监听对象中某一个属性的变化, 可以之间监听子属性
const vm = new Vue({
el: '#app',
data: {
info:{
username: 'zs',
age: 20
}
},
watch: {
// 定义对象格式的侦听器
info: {
handler(newVal) {
console.log('触发侦听器-------',newVal);
},
// 开启深度监听,只要对象中的任何一个属性变化了,都会触发 对象的侦听器
deep: true
},
// 上述监听,是只要对象中的属性发生变化,都会触发监听器,
// 但是有时候我们只需要监听某个属性的变化,所以我们可以监听对象单个舒心改变
// 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号
'info.username'(newVal) {
console.log(newVal);
}
}
})
侦听器的格式:
- 方法格式的侦听器:
- 缺点1: 无法在刚进入页面的时候,自动触发
- 缺点2: 如果侦听的是一个对象,如果对象中的属性发生了变化, 不会触发侦听器!!!
- 对象格式的侦听器:
- 好处1:可以通过
immediate
选项,让侦听器自动触发!!!
- 好处2: 可以通过
deep
选项,让侦听器深度监听对象中的每个属性变化!!!
2.8 计算属性
计算属性指的是通过一系列运算
之后,最终得到的一个值。这个动态计算出来的属性值
可以被模版结构或methods方法使用。
- 计算属性:
- 特点: 定义的时候,要被定义成方法;在使用计算属性的时候,当普通的属性使用即可
- 好处: 实现了代码的复用;只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值
2.9 axios
axios:是前端最火的、专注于数据请求
的库。
相关文当网站:http://www.axios-js.com/zh-cn/docs/、https://www.axios-http.cn
发起 GET 请求:
axios({
// 请求方式
method: 'GET',
// 请求的地址
url: 'http://www.liulongbin.top:3006/api/getbooks',
// URL 中的查询参数
params: {
id: 1
}
}).then(function (result) {
console.log(result)
})
发起 POST 请求:
document.querySelector('#btnPost').addEventListener('click', async function () {
// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
// await 只能用在被 async “修饰”的方法中
const { data: res } = await axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: {
name: 'zs',
age: 20
}
})
console.log(res)
})
注意:关于axios这里不做过多介绍, 更多用法请参考官方文档
每次使用axios时,在临时导入, 这样如果多个地方使用,那每个地方都使用,非常麻烦, 我们可以把axios挂在到Vue上。我们知道每一个组件都是一个Vue的实例
那今后要在那个组件调用axios发起请求, 直接使用this.$http.xxx发起请求,但是这样做不利于API接口的复用。
2.10 vue-cli
什么是vue-cli?
vue-cli是Vue.js开发的标准工具
。它简化了程序员基于webpack创建工程化的Vue项目的过程。中文官方地址:https://cli.vuejs.org/zh/
Vue-cli的安装和使用
vue-cli是npm上的一个全局包
,使用npm install
命令,即可方便的把它安装到自己的电脑上:npm install -g @vue/cli
,基于vue-cli快速生成工程化的Vue项目:vue create 项目的名称
执行vue create xxx 的项目创建过程
-
-
-
-
-
-
创建完之后,项目的结构目录如下:
-
vue中src目录的构成:
- assets文件件: 存放项目中用到的静态资源,例如:css样式表、图片资源
- components文件夹:程序员封装的、可复用性的组件,都要放到components目录下
- main.js是项目的入口文件。整个项目的运行,要先执行main.js
- App.vue是项目的根组件
-
在工程化的项目中,vue要做的事情很单纯: 通过main.js
把App.vue
渲染到index.html
的指定区域中。
App.vue
用来编写待渲染的模版结构
index.html
中需要预留一个el区域
main.js
把App.vue渲染到了index.html所预留的区域中
// 创建vue的实例对象
new Vue({
// 把render 函数指定的组件,渲染到HTML页面中 (直接渲染到#app区域,直接替换#app区域)
render: h => h(App),
}).$mount('#app')
// vue 中$mount()方法,作用和 el 属性完全一样
2.11 vue组件
什么是组件化开发?
组件化开发指的是:根据封装
的思想,把页面上可重用的UI结构封装为组件
,从而方便项目的开发和维护。
vue是一个支持组件化开发
的前端框架。vue中规定:组件的后缀名
是.vue
。之前接触到的App.vue文件本质上就是一个vue组件。
每个.vue组件都由三部分构成,分别是:
template
: 组件的模版结构
script
:组件的JavaScript行为
style
:组件的样式
其中每个组件中必须包含template模版结构
,而script行为
和style样式
是可选的组成部分
。
2.11.1组件的基本使用
vue规定:每个组件对应的模版结构
,需要定义到结点中
。
<template>
<!-- 当前组件的 DOM 结构, 需要定制到template标签的内部 -->
</template>
- 注意:
- template是vue提供的
容器标签
,只起到包裹性质的作用
,它不会被渲染为真正的DOM元素
- template中只能包含唯一的根节点
<template>
<!-- 当前组件的 DOM 结构, 需要定制到template标签的内部 -->
<div class="test-box">
<h3>这是用户自定义的 Test.vue -----{{ username }}</h3>
<button @click="changeName">按钮事件</button>
</div>
</template>
<script>
// 默认导出,固定写法
export default {
// data数据源
// 注意: .vue组价中的data不能和之前一样, 不能指向对象,.vue中的data必须是一个函数
// 错误写法 :data: {username: 'zs' }
//正确写法:
data() {
return {
username: 'zs'
}
},
methods: {
// 在组件中, this就表示当前组件的实例对象
changeName() {
console.log(this);
this.username = '玩哈哈'
}
}
}
</script>
<!-- 如果css需要使用less语言来写, 需要指定属性 lang="less" 该属性默认值css-->
<style lang="less">
.test-box {
background-color: red;
h3 {
color: white;
}
}
</style>
2.11.2 组件之间的父子关系
使用组件的三个步骤:
- 使用import语法
导入需要的组件
import Left from '@/components/Left.vue'
- 使用
components
节点注册组件 export default { name: 'App', components: { Left } }
以标签的形式
使用刚才注册的组件
2.11.3 私有组件和全局组件
通过coponents注册的是私有组件
,例如在组件A
的conponents节点下,注册了组件F
。则组件F
只能在组件A
中使用,不能被用在组件C
中
这时候我们可以采用注册全局组件, 在vue项目的main.js
入口文件中通过Vue.component()
方法,可以注册全局组件。
// 导入需要全局注册的组件
import Test from '@/components/test.vue'
// 参数一: 字符串格式表示组件的注册名称 参数二: 需要被全局注册的那个组件
Vue.component('Test',Test)
注意: 在组件的内部不能使用自己, 会造成循环使用, 终端会报错
2.11.4 组件的props
props是组件的自定义属性
,在封装通用组件
的时候, 合理的使用props可以极大的提高组件的复用性
。语法格式如下:
上述方式可以在组件初始化的时候,给定一个初始值, 但是我们发现这里传递的只能是字符串。 那么我们如果想要传递数字,如何传递了?
解决方案: 我们可以通过v-bind属性绑定
传值, 因为通过v-bind
绑定之后, 等候后面的语句会当成js语句执行。
注意: props是只读的,vue规定,组件封装中的自定义属性是只读的
,程序员不能直接修改props的值。否则会直接报错
如果想要修改props的值, 可以把props的值转存到data中
,因为data中的数据都是可读可写的!
如果我们不想再创建组件时传递一个初始值
, 但是我们又希望自定义属性有一个默认值。那我们可以在声明自定义属性的时候,可以通过default
来定义属性的默认值
上述定义的自定属性,我们可以传可以不传。但是如果我们需要使用者一定要传的话,我们可以通过require
属性来指定,使用组件时必须要传递该属性的初始值。注意: required属性和default默认值 没有任何关系
如果使用required属性
修饰,使用组件时没有传递初始值,则终端会报如下错误:
组件之间的冲突问题: 默认情况下,写在.vue组件中的样式会全局生效
,因此很容易造成多个组件之间的样式冲突关系
。
导致组件样式冲突根本原因:
- 单页面应用程序中,所有的组件的DOM结构,都是基于
唯一的index.html页面
进行呈现的
- 每个组件中的样式, 都会
影响整个index.html页面中的DOM元素
我在Left组件中设置了
标签的颜色, 并没有在app.vue和Right.vue
中设置标签的颜色,但是云信结果发现,页面中所有的
的字体颜色都变成绿色了。 这就是组件之间的样式冲突问题
解决方案: 每个组件中的标签都添加一个自定义属性,然后通过属性选择器来设置CSS,就可以达到单独设置某个组件内的样式,达到解决不同组件的样式冲突问题。
上述方法是我们自己添加手动在组件中每个标签上添加自定义属性, 然后使用属性选择器来设置CSS。但是 Vue已经给我们解决了这个问题, 只需要我们在写样式时,加上scoped 属性
,那么Vue会自动给组件的每个标签加上一个自定义属性。
当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要使用到/deep/ 属性
:
2.12 生命周期
生命周期
(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁
整个阶段,强调的是一个时间段
。
生命周期函数
:是有vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行
。
注意: 生命周期
强调的是时间段
,生命周期函数
强调的是时间点
。
组件生命周期函数的分类:
生命周期图示: 可以参考vue官方给出的“生命周期图示”,进一步理解组件生命周期执行的过程:https://v2.cn.vuejs.org/v2/guide/instance.html
<script>
export default {
props: ['info'],
data() {
return {
message: 'hello vue.js',
// 定义 books 数组,存储的是所有图书的列表数据。默认为空数组!
books: []
}
},
watch: {
message(newVal) {
console.log('监视到了 message 的变化:' + newVal)
}
},
methods: {
show() {
console.log('调用了 Test 组件的 show 方法!')
},
// 使用 Ajax 请求图书列表的数据
initBookList() {
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', () => {
const result = JSON.parse(xhr.responseText)
console.log(result)
this.books = result.data
})
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
xhr.send()
}
},
// 创建阶段的第1个生命周期函数
beforeCreate() {
// console.log(this.info)
// console.log(this.message)
// this.show()
},
created() {
// created 生命周期函数,非常常用。
// 经常在它里面,调用 methods 中的方法,请求服务器的数据。
// 并且,把请求到的数据,转存到 data 中,供 template 模板渲染的时候使用!
this.initBookList()
},
beforeMount() {
// console.log('beforeMount')
// const dom = document.querySelector('#myh3')
// console.log(dom)
},
// 如果要操作当前组件的 DOM,最早,只能在 mounted 阶段执行
mounted() {
// console.log(this.$el)
// const dom = document.querySelector('#myh3')
// console.log(dom)
},
beforeUpdate() {
// console.log('beforeUpdate')
// console.log(this.message)
// const dom = document.querySelector('#pppp')
// console.log(dom.innerHTML)
},
// 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中
updated() {
// console.log('updated')
// console.log(this.message)
// const dom = document.querySelector('#pppp')
// console.log(dom.innerHTML)
},
beforeDestroy() {
console.log('beforeDestroy')
this.message = 'aaa'
console.log(this.message)
},
destroyed() {
console.log('destroyed')
// this.message = 'aaa'
}
}
</script>
2.13 组件之间的数据共享
在项目开发中,组件之间的最常见的关系分为如下两种: 父子关系、兄弟关系
父组件向子组件共享数据: 父组件向子组件共享数据需要使用自定义属性
,示例代码如下:
子组件向父组件共享数据: 子组件向父组件共享数据使用自定义事件
。示例代码如下:
兄弟组件的数据共享:在vue2.x中,兄弟组件之间数据共享的方案是EventBus
- EventBus的使用步骤:
- 创建
eventBus.js
模块,并向外共享一个Vue的实例对象
- 在数据
发送方
,调用bus.$emit('事件名称',要发送的数据)方法
触发自定义事件
- 在数据
接受放
,调用bus.$on('事件名称','事件处理函数')方法
注册一个自定义事件
2.14 ref
什么是ref引用?
ref是用来辅助开发者在不依赖jQuery的情况下
,获取DOM元素或组件的引用。
每个vue的组件实例,都包含一个$refs对象
,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的$refs对象指向一个空对象
如果想要试用ref引用页面上的组件实例
,则可以按照如下方式进行操作:
注意:当我们通过$refs拿到组件的实例对象
,就可以调用该组件的方法和属性
有时候我们有如下需求, 当我们修改DOM元素之后, 我们希望在DOM结构更新之后,在执行接下来的动作。
这时候vue给我们提供了this.$nextTick(cb)
方法, 会把cb回调推迟到下一个DOM更新周期之后执行
。通俗的理解是:等组件的DOM更新完成之后,在执行cb回调函数,从而能保证cb回调函数可以操作到最新的DOM元素。
2.15 动态组件
什么是动态组件?
动态组件指的是动态切换组件的显示与隐藏
。
component
:该标签是vue内置标签, 起作用:组件的占位符, is属性的值,表示要渲染组件的名称(名称是组件在componenes【节点下注册的名称)
当我们来回切换组件的时候, 我们可以发现一个问题, 当我们切换到另外一个组件时, 之前显示的那个组件已经被销毁了, 有时候根据需求,我们不希望组件被销毁。
针对这种情况, vue提供了keep-alive
内置标签来,保持组件的状态, 该标签的作用:可以把内部的组件进行缓存,而不是销毁组件
有些时候我们需要监听组件的缓存和激活的时间点,用来做一些事件。vue也给我们提供了keep-alive
对应的生命周期函数:
- 当组件
被缓存时
,会自动触发组件的deactivated
生命周期函数
- 当组件
被激活时
,会自动触发组件的activated
生命周期函数。
我们可以发现,activated
生命周期方法,在第一次创建的时候,也会执行
有时候我们可能在keep-alive
同时显示多个组件, keep-alive
给我们提供了iinclude属性
,用来指定只有名称匹配的组件
会被缓存。多个组件名之间使用英文的逗号
分割:
<keep-alive include="Left">
<component :is="componentName"></component>
</keep-alive>
除了include
属性之外,还有exclude属性: 指定哪些组件不需要被缓存
,需要注意的是不要同时使用include和exclude这个两个属性
keep-alive
在使用include
和exclude
属性时,会根据组件的name
来筛选,所以的组件要被keep-alive
条件缓存时, 就必选显示申明一个name选项。如果在声明组件
的时候,没有为组件指定name名称
,则组件的名称默认就是注册时候的名称
。但是当我们提供了name属性
之后,组件的名称,就是name属性
2.16 插槽
什么是插槽?
插槽(Slot)
是vue为了组件的封装着
提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分
定义为插槽。
vue官方规定: 每一个slot插槽,都要有一个name
名称, 如果省略了slot的 name属性,则有一个默认的名称叫做default
我们还可以定义多个插槽,然后根据插槽的名字
来填充自定义的内容, 我们可以使用v-slot属性
来指定内容填充到那个插槽上,v-slot:xxx还可以简写为#xxx
我们还可以给插槽指定默认值,如果用户没有指定内容,那么该插槽区域就会展示默认内容,如果用户指定了内容,那就展示用户的内容
我们在封装组件的时候,为预留
提供属性对应的值,这种用法叫做作用域插槽
,示例代码如下:
注意:默认情况下没有传值的话,接收的是一个{} 对象
2.17 自定义指令
什么是自定义指令?
vue官方提供了v-text、v-for、v-model、v-if等常用的指令。除此之外vue还允许开发者自定义指令。
vue中的自定义指令分为:私有自有自定义指令、全局自定义指令
在每个vue组件中,可以在directives
节点下声明私有自定义指令
。示例代码如下:
bind函数只调用1次
:当指令第一次绑定到元素时调用,当DOM更新时bind函数不会被处罚
。update
函数会在每次DOM更新时
被调用。
如果bind
和update
函数中的逻辑完全相同
,则对象格式
的自定义指令可以简写成函数格式
:
全局共享的自定义指令需要通过Vue.directive()
进行声明,示例代码如下:
2.18 eslint
ESLint:可组装的JavaScript和JSX检查工具. 代码规范工具
中文网: https://zh-hans.eslint.org
使用vue create xxx
在创建项目, 在插件选择这一步,选择ESlint(默认选中)
创建完项目之后,项目的结构目录, 可以发现多了一个.eslintrc.js
的目录文件:
根据团队的代码规则,可以在.eslintrc.js
配置文件中,配置想要支持的规则。 具体的规则参考ESlint的官网。
这里有两款辅助ESlint的插件,安装方式参考:https://blog.csdn.net/yyzx_yyzx/article/details/125708717
2.19 路由
什么是路由? 路由(Router)就是对应的关系
前端路由:通俗易懂的概念Hash地址
与组件
之间的对应关系。
- 前端路由的工作方式:
- 用户
点击了
页面上的路由连接
- 导致了
URL地址栏中
的Hash值
发生了变化
前端路由监听了到Hash地址的变化
- 前端路由把当前
Hash地址对应的组件
渲染到浏览器中
2.19.1 实现简易的前端路由
2.19.2 vue-router的基础使用
什么是vue-router?
vue-router 是vue.js官方给出的路由解决方案
。它只能结合vue项目进行使用,能够轻松的管理SPA项目中组件的切换。 vue-router的官方文档地址 :https://router.vuejs.org/zh/
- vue-router安装和配置的步骤:
- 安装vue-route包
npm install [email protected] -S
- 创建路由模块
- 导入并挂载路由模块
- 声明
路由连接
和占位符
创建路由模块:在src
原代码目录下,新建router/index.js
路由模块,并初始化如下代码:
其实当我们安装和配置了vue-router后,就可以使用router-link 来替代普通的 a 连接:
2.19.3 嵌套路由
嵌套路由: 通过路由实现组件的嵌套展示
,叫做嵌套路由。
除了重定向路由
实现第一次进页面显示默认页面,默认子路由
也可以实现
2.19.4 动态路由
动态路由:把Hash地址中可变的部分
定义为参数项
,从而提高路由规则的复用性
。在vue-route中使用英文的冒号(:)
来定义路由的参数项。示例代码如下:
例如我们有如下需求: 我么希望根据电影的id值,展示对应电影的详情信息。
这时候我们可以为路由规则开启props传参
,从而方便的拿到动态参。
- 注意:
- 在hash地址中, / 后面的参数项, 叫做路径参数。 在路由
参数对象
中 ,需要使用this.$route.params
来访问路径参数
- 在hash地址中, ?后面的参数项,叫做查询参数。在路由参数列表中,需要使用
this.$route.query
来访问查询参数
- 在
this.route
中, path
只是路径的一部分; fullpath
是完整的地址
- 例如: /movie/2?name=zs&age=20 是fullpath的值; /movie/2是path的值
2.19.5 声明式导航 & 编程时导航
在浏览器中,点击链接
实现导航的方式, 叫做声明式导航
。 例如:
在普通网页中点击 链接
、vue项目中点击
都属于声明式导航
在浏览器中,调用API
实现导航的方式,叫做编程式导航
。例如普通网页中调用locatio.href
跳转到新的页面的方式,属于编程式导航
-
vue-router 中提供了许多的编程式导航API,其中最常见的导航API分别是:
- this.$router.
push
(''hash 地址): 跳转大指定的hash地址, 并增加
一条历史记录。
- this.$router.
replace
(‘hash 地址’): 跳转到指定的hash地址,并替换掉当前的
历史记录
- this.$router.
go
(数值n) : 可以在浏览历史中前进和后退
在实际开发中,一般只会前进和后退一层页面。 因此vue-router提供了如下两个便捷防范:
2.19.6 导航守卫
每次发生路由的导航跳转
时, 都会触发全局前置守卫
。因此,在全局前置守卫中 程序员可以对每个路由进行访问权限
的控制:
当前用户拥有
后台主页的访问权限,直接放行:next()
当前用户没有
后台主页的访问权限, 强制其跳转到登录页面
:next(‘/login’)
当前用户没有后台主页的访问权限,不允许跳转到后台主页
: next(false)
本文学习过程中的示例代码:https://gitee.com/guoweiyong/web_vue_study