yarn:安装 https://blog.csdn.net/qq_40738077/article/details/101843070
npm查看插件版本:例如 npm view webpack version/versions
1.前端工程化
实际的前端开发:
-
模块化 js模块化,css模块化,资源的模块化
-
组件化 复用现有的ui结构,样式,行为
-
规范化 目录的结构划分,编码规范化,端口规范化,文档规范化,git规范化
-
自动化 自动化构建,自动部署,自动化测试
webpack的基本使用:webpack是前端工程化的具体解决方案,
功能:提供了友好的前端模块化支持,以及代码压缩混淆,处理浏览器端的JavaScript的兼容性,性能优化的的强大功能。
2.webpack基础,创建隔行变色的案例
- 创建一个文件夹,这个文件夹不能有中文。
- 点击进入这个目录文件夹,在此目录中打开cmd命令,运行 npm init -y 命令,初始化包管理配置文件package.json。
- 在当前目录下创建一个src目录,在src目录下创建一个index.html首页和一个index.js脚本文件。
- 初始化首页的基本结构。(使用快捷命令 ul>li{这是第$个li}*9)
- 在当前文件目录中 (不是在src目录中)运行 npm install jquery -S ,安装jQuery
注意: -S 也就是 --save,使用之后就会将将jQuery存放在package.json的dependencies节点之下,也可以不使用-S或者–save,默认就会执行到dependencies节点下,但使用可以更清楚。
- 在index.js中写jQuery代码实现隔行变色,在常规模式下,我们需要在index.html下导入jQuery封装库的代码,在这里我们就直接在index.js中使用import导入jQuery。
//使用es6语法导入jquery
import $ from 'jQuery' //导入的内容必须是字符串
//定义jQuery的入口函数
$(function () {
//奇数行变色
$("li:odd").css("background-color", "red");
//偶数行变色
$("li:even").css("background-color", "pink");
})
-
在index.html中导入index.js脚本,运行,发现控制台浏览器不支持这样高级的语法,这时我们就想到了webpack,它可以帮助我们将高级的语法转换成低级的语法。
-
webpack的安装,在当前文件目录下非src目录下运行npm install webpack@版本号 webpack-cli@版本号 -D,-D表示的是在开发阶段使用。然后这个包就会在package.json的devDependencies节点下。-D也用–save-dev表示
-
配置webpack信息,在当前项目下新建一个webpack.config.js文件
//node.js的导出语法,向外导出webpack的配置对象 module.exports = { //代表webpack运行的模式,可选值 development 和 production mode: 'development' //开发模式下使用 }
-
在package,json文件中修改scripts脚本信息,也就是webpack启动打包。
"scripts": {
"dev": "webpack"
},
- 使用npm run dev 执行打包,就会在当前文件目录下出现一个dist文件夹,里面就有一个main.js文件。也就是jQuery和自己写的代码的整合。
1. webpack中的默认约定
在webpack 4.x和5.x的版本中:
- 默认打包的文件是src下的index.js文件。
- 默认的输出文件路径是dist文件下的main.js文件
修改默认约定:
在webpack.config.js配置文件中修改默认约定,通过entry节点指定打包的入口,通过output节点指定打包的出口。
//导入node的语法使用
const path = require('path')
//node.js的导出语法,向外导出webpack的配置。
module.exports = {
mode: 'development',
//entry 指定要处理的哪个文件
entry: path.join(__dirname, './src/index1.js'),
//output指定打包的文件存放在哪里
output: {
//存放的目录
path: path.join(__dirname, 'dist1'),
//生成的文件
filename: 'bandle.js'
}
}
2.webpack插件
- webpack-dev-server使用实时打包的http服务器
每当修改代码后自动生成,无需重启,通过locahost:8080就可以访问,生成的是一个虚拟的内存文件。
通过 npm install webpack-dev-server@版本号 -D命令下载到当前文件下(3.11.2版本)
修改package.json下的scripts节点的dev命令
{
"scripts": {
"serve": "webpack serve"
}
}
这时候,这个插件打包的文件就会存放在内存中,并不是在物理磁盘中,在src的index.html文件中使用/打包后的文件.js,脚本导入,因为/代表的是虚拟路径。
使用locahost:8080路径访问,就会出现一堆文件夹,我们还需要点击src目录才能将页面展示出来。这样就显得很麻烦,这样就需要使用下面这个插件。
- html-webpack-plugin,提取src目录中的文件到locahost:8080中,在不需要每次点击src目录中才能访问
使用npm i --save-dev html-webpack-plugin@版本号下载到当前目录中。
//导入html插件,得到一个构造函数
const HTMLplugin = require('html-webpack-plugin')
//创建html插件的实例对象
const htmlplugin = new HTMLplugin({
template: './src/index.html', //指定源文件的存放路径
filename: './index.html', //指定生成文件的存放路径
})
module.exports = {
mode: 'development',
plugins: [htmlplugin], //使用plugins节点是htmlplugin生效
}
- devServer的配置
devServer配置可以在重启项目后直接跳转到网页,无需复制地址再进入浏览器。
devServer: {
open: true, //启动项目自动打开网页
host:'IP地址',//启动项目后的ip地址
port: 8081, //打开网页的端口号
}
3.webpaack中loader的使用
1. css-loader的配置
- 在src目录下的index.js中导入css文件,例如 import ‘./css/index.css’
- 使用 npm i style-loader@版本号 css-loader@版本号 -D命令下载。
- 在webpack.config.js文件中的module的rules数组中创建。
module: {
rules: [{
test: /\.css$/i, //使用正则表达式匹配相关的css文件
use: ["style-loader", "css-loader"], //循序不能改变,是从后往前执行查找
}, ],
},
2. less-loader的配置
- 在src目录下index.js中导入less文件,例如import ‘./css/index.less’
- 使用npm install less less-loader --save-dev命令下载
- 在webpack.config.js文件中的module的rules数组中创建(紧跟前面的css-loader对象写)
module: {
rules: [{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
}, {
test: /\.less$/i,
use: ["style-loader","css-loader","less-loader",],
}, ],
},
4.webpack打包
- 配置build命令
在package.json文件的scripts节点下新增build命令。
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production"
},
–model是一个参数项,用来指定webpack的运行模式,production代表生产模式,进行代码压缩和性能优化。
注意: 通过 --model指定的参数项,会覆盖webpack.config.js中的model配置
5.vue基础
1.什么是vue
1. 是一套现成的解决方案,程序员必须遵守框架的规范,编写自己的业务功能。
2. 学习vue,就是学习vue框架中规定的用法。
3. 要学习vue的指令,组件(对UI结构的复用),路由,vuex,vue组件库等。
4. 只有把上面差不多学完才能有开发vue项目的能力。
vue的特性:
-
数据驱动视图
1. 数据的变化会驱动视图的自动更新
2. 好处就是程序员把数据维护,那么页面结构就会自动被vue渲染出来
3. 注意,数据驱动视图是单向的数据绑定。 -
双向数据绑定
1. 网页中,from表单采集数据,Ajax用来提交数据
2. js数据变化,会被自动渲染到页面上
3. 页面上的表单采集的数据发生变化的时候,会被vue自动的获取,并更新js数据。
MVVM思想
MVVM是vue实现数据驱动试图和双向数据绑定的核心思想,MVVM指的是Model,View和ViewModel,他把每个html页面都拆分成了这三个部分。
- Model表示当前页面渲染时所依赖的数据源。
- View 表示当前页面所渲染的Dom结构。
- ViewModel表示的vue的实例,它是MVVM的核心
2.vue指令
vue中的指令按照不同的功能可以分为六大类:
- 内容渲染指令
- 属性绑定指令
- 事件绑定指令
- 双向绑定指令
- 条件渲染指令
- 列表渲染指令
3.内容渲染指令
用来辅助开发者渲染DOM元素的内容,常用的渲染指令有以下三个
- v-text指令,会覆盖元素内原有的内容。
- {{}},插值表达式实际开发中用的最多,只是内容的占位符,不会覆盖元素的内容,只能用于元素的内容节点中,不能用于元素的属性节点中
- v-html,可以将带html标签的字符串选染成真正的html标签
4.属性绑定指令
1. v-bind,为元素的属性动态绑定属性值,因此它使用的特别多,所以简写为一个:
<input type="text" :placeholder="data属性">
5.事件绑定指令
v-on:用于事件绑定,也就是鼠标或者键盘事件,其中需要在data和el同级下创建methods对象,里面就是存放的函数事件。
v-on也是使用很频繁,所以也有简写方式,例如鼠标点击事件就是@click=“事件名”。
6.this指向本身对象
在创建vue对象时,访问vue对象就会出现许多属性,里面包含许多值,可以操作data值,方法等。
<body>
<div id="app">
<button @click="show"> 点击按钮</button>
</div>
<script src="./lib/vue.min.js"></script>
<script>
//创建vue对象,名字随便定义
const vue = new Vue({
el: "#app",
data: {
},
methods: {
show() {
console.log(vue); //输出本身的vue对象
vue.log() //通过自身的vue对象本身访问log方法。
},
log() {
console.log("nihao")
}
},
})
</script>
</body>
上面这种方式可以在对象外使用,但大多数情况只在vue对象中使用,这样使用对象.的形式就很麻烦,所以在使用vue时就可以在对象中直接使用this.的形式指向属性或者方法。
<body>
<div id="app">
<button @click="show"> 点击按钮</button>
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
},
methods: {
show() {
console.log(this);
this.log()
},
log() {
console.log("nihao")
}
},
})
</script>
</body>
7.$event
e v e n t 是指当前触发的是什么事件,例如鼠标事件,键盘事件等, ∗ ∗ event是指当前触发的是什么事件,例如鼠标事件,键盘事件等,** event是指当前触发的是什么事件,例如鼠标事件,键盘事件等,∗∗event.target**则指的是事件触发的目标,即哪一个元素触发了事件,这将直接获取该dom元素。
获取$event的两种方式:
- 在事件中没有传参的时候,默认方法会携带一个$event。
<body>
<div id="app">
<button @click="show"> 点击按钮</button>
<input type="submit" @click="show">
<!-- input和button中不能传参,即使有括号也不行 -->
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
},
methods: {
show(e) {
console.log(e);
},
},
})
</script>
</body>
- 如果在方法中有传入的参数,那么就需要指定$event,也就是固定写法。
<body>
<div id="app">
<button @click="show(2,$event)"> 点击按钮</button>
<input type="submit" @click="show(1,$event)">
<!-- 如果传入了其他参数就必须指定传入$event -->
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
},
methods: {
show(n, e) {
console.log(n);
console.log(e);
},
},
})
</script>
</body>
$event其他类型获取
methods: {
clickfun(e) {
// e.target 是你当前点击的元素
// e.currentTarget 是你绑定事件的元素
//获得点击元素的前一个元素
e.currentTarget.previousElementSibling.innerHTML
//获得点击元素的第一个子元素
e.currentTarget.firstElementChild
// 获得点击元素的下一个元素
e.currentTarget.nextElementSibling
// 获得点击元素中id为string的元素
e.currentTarget.getElementById("string")
// 获得点击元素的string属性
e.currentTarget.getAttributeNode('string')
// 获得点击元素的父级元素
e.currentTarget.parentElement
// 获得点击元素的前一个元素的第一个子元素的HTML值
e.currentTarget.previousElementSibling.firstElementChild.innerHTML
}
},
8.事件修饰符
事件修饰符 | 说明 |
---|---|
.stop | 阻止事件冒泡,主要争对点击事件,等同于event.stopPropagation() |
.prevent | 阻止默认行为,例如a标签点击会跳转,阻止它不让跳转,event.preventDefault() |
.capture | 与事件冒泡相反,以捕获的形式触发当前处理函数(不常用) |
.once | 绑定的事件指触发一次(不常用) |
.self | 只有在event.target是当前元素自身的时候触发该事件处理函数(不常用) |
9.双向数据绑定
双向数据绑定也就是数据发生变化的时候,试图也会随之发生变化,当视图发生变化的时候,数据也会随之发生变化。在vue中双向数据绑定常用到v-model,v-model本质上是一个语法糖。
<input type="text" @input="message = $event.target.value"> 等同于 <input type="text" v-model="message">
v-model获取数据是实时的,只要视图或者数据一变化就会触发,v-model可以理解为是获取和修改标签元素中的value属性。v-model可以下拉框,复选框,单选框中使用,都是获取value属性。
v-model指令的修饰符
修饰符 | 作用 |
---|---|
.number | 将数据转为数值型,v-model默认是String类型 |
.trim | 去掉字符串首尾的空格 |
.lazy | 在change时而非input时更新,但鼠标离开表单时触发 |
上面的三个修饰符可以联合起来用,中间部分也是以.的形式分割。
10.条件渲染指令
条件渲染指令是用来辅助开发者按需控制DOM的显示与隐藏。条件渲染指令有v-if和v-show两个。
- v-show原理是动态添加或删除display:none的样式,来实现元素的显示与隐藏。
如果要频繁的切换元素的显示状态,使用v-show更好。
- v-if原理是动态创建或删除元素,实现元素的显示与隐藏
如果是刚进入页面的时候,某些元素不需要显示的时候,使用v-if。
在实际的开发中,可以直接使用v-if,现在电脑的性能也不差。
v-if可以结合v-else-if和v-else使用,使用的时候必须保持在同一级上,也就是标签元素必须是兄弟关系
11.列表渲染指令
在vue中用来一个数组遍历或者牧举一个对象循环显示的时候,就会用到v-for列表渲染指令,常常结合到in或者of使用。
<body>
<div id="app">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>索引</th>
<th>id</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in list" :key="item.id">
<td>{{index+1}}</td>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
</tr>
</tbody>
</table>
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "张三" },
{ id: 2, name: "王五" },
{ id: 3, name: "李四" },
]
}
})
</script>
</body>
**官方建议:**只要用到v-for指令,那么一定要绑定一个:key属性,而且,尽量把id作为key的值,官方对key的值类型也是有要求的,字符串或者数字类型。
12.vue过滤器
过滤器是vue开发者提供的功能,常用于文本格式化,过滤器可以用在插值表达式和b-bind属性绑定中,中间使用一个竖线分割(也就是管道符),过滤器只在vue2中使用,vue3中过滤器就去掉了。
过滤器要在el和data同一节点下创建一个filters节点,filters里面类似函数声明一样,但必须至少要有一个返回值,过滤器中的形参都是管道符前面那个值。
<body>
<div id="app">
{{message | nn}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
message: "hello world"
},
filters: {
nn(n) {
let a = n.charAt(0).toUpperCase() //获取字符串的第一个字符,转变为大写
let b = n.slice(1); //获取字符串索引为1及后面所有的字符
return a + b
}
}
})
</script>
</body>
上面这种在内部的写法称为私有过滤器,有私有过滤器就会有全局过滤器,私有过滤器只能在el挂在的标签里使用,如果其他的挂在要使用的话就必须在创立一个一模一样的私有过滤器,这时候就需要全局过滤器,无论谁都可以使用,全局过滤器使用 Vue.filter()创建,放在所有代码的最前面,否则不起作用,全局过滤器方法接受两个参数,第一个参数是过滤器的名字,第二个参数是过滤器的处理函数,接受的形参也是管道符前面的值。
<body>
<div id="app">
{{message | nihao}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
Vue.filter("nihao", (value) => {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1) + "---"
})
const vue = new Vue({
el: "#app",
data: {
message: "hello world"
},
filters: {
nn(n) {
let a = n.charAt(0).toUpperCase()
let b = n.slice(1);
return a + b
}
}
})
</script>
</body>
**注意:**如果全局过滤器与私有过滤器的名字冲突了,首先按照就近原则调用自己的私有过滤器。
过滤器可以串联,后一个过滤器接受前一个过滤器的返回值
过滤器本质上就是一个JavaScript函数,因此可以接受参数。
<div id="app">
{{message | nihao("我的世界","朋友")}}
</div>
<script>
Vue.filter("nihao", (value, x1, x2) => {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1) + x1 + x2
})
</script>
上面的vue代码中,过滤器本质上接受了三个参数,而在传入参数的时候只接收两个,因为过滤器默认第一个参数必须是管道符前面的那个参数,传入的参数则从第二个开始。
13.vue侦听器
watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的动作,watch表示侦听器的节点,与el,data节点同级,侦听器其实也就是一个函数,但侦听器的函数名必须与要被侦听的属性名相同,侦听器分为两种格式,第一种是方法格式,第二种是对象格式。
- 方法格式侦听器,缺点是无法在刚进入页面的时候自动触发。
<body>
<div id="app">
<input type="text" v-model="name">
{{name}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
name: ""
},
watch: {
//侦听器的名字必须与被侦听的属性名一样,也就是监听name属性的变化
//newName表示变化后的新值
//oldName表示变化之前的旧值
name(newName, oldName) {
if (newName == "") return
$.get("https://www.escook.cn/api/finduser/" + newName, (result) => {
console.log(result);
})//这个请求路径判断名字是否被占用
}
}
})
</script>
</body>
- 对象格式的侦听器,好处是可以通过immediate选项让侦听器自动触发。
<body>
<div id="app">
<input type="text" v-model="name">
{{name}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
name: "n"
},
watch: {
name: {
//handler表示侦听器的处理函数
handler(newName, oldName) {
console.log(newName, oldName);
},
//immediate默认值是false,true表示刚进入页面就出发一次
immediate: true,
}
}
})
</script>
</body>
**深度监听:**如果data节点下的属性是一个对象,监听的也是一个对象,对象里的属性如果发生变化,那么侦听器不会触发,这时候就需要深度监听,监听这个对象中的所有属性,而且深度监听也只能以对象格式的侦听器使用。
<body>
<div id="app">
<input type="text" v-model="info.name">
{{info.name}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
info: {
name: "nn"
}
},
watch: {
info: {
//handler表示侦听器的处理函数
handler(newName) {
console.log(newName.name);
},
//immediate默认值是false,true表示刚进入页面就出发一次
immediate: true,
//深度监听,只要对象中任何一个属性变化了,都会触发对象的侦听器
deep: true
}
}
})
</script>
</body>
**侦听器其他用法:**如果侦听对象中的属性发生变化时,那么新值和旧值都要通过.的形式获取,这样就显得很麻烦,所以我们直接监听对象中的属性,那么就需要以对象.属性的形式作为侦听器的名字(外部必须包个单引号),这时候也就不需要深度监听了。
<body>
<div id="app">
<input type="text" v-model="info.name">
{{info.name}}
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
info: {
name: "nn"
}
},
watch: {
'info.name': {
//handler表示侦听器的处理函数
handler(newName, oldName) {
console.log(newName, oldName);
},
//immediate默认值是false,true表示刚进入页面就出发一次
immediate: true,
//深度监听,只要对象中任何一个属性变化了,都会触发对象的侦听器
// deep: true
}
}
})
</script>
</body>
14.vue计算属性
计算属性是指通过一系列运算之后,最终得到的一个属性值,这个动态计算出来的属性值可以被模板结构或methods方法使用,本质上就是一个tata中的属性。
计算属性的出现主要是减少在模板中的逻辑,如果在模板中逻辑或者表达式过于复杂,那么就难以维护,所以利用计算属性直接将复杂的逻辑类似与封装之后就可以直接拿去使用,与data中的属性是同一等级,所以,对于任何复杂逻辑,都应该使用计算属性。
计算属性的创建也需要在data,el等同等级别下创建一个computed节点,内部也是一个函数。
特点:
- 如果数据源被改变,则计算属性会被自动计算求值。
- 实现代码的复用
<body>
<div id="app">
<div><button @click="show">确认显示</button></div>
<div><span>R</span><input type="text" v-model.number.trim="R"></div>
<div><span>G</span><input type="text" v-model.number.trim="G"></div>
<div><span>B</span><input type="text" v-model.number.trim="B"></div>
<div class="box" :style="{backgroundColor: rgb}">
{{rgb}}
</div>
</div>
<script src="./lib/vue.min.js"></script>
<script>
const vue = new Vue({
el: "#app",
data: {
R: 0,
G: 0,
B: 0
},
//所有计算属性都要定义在computed节点之下
//计算熟悉在定义的时候要定义成方法格式
computed: {
//rgb作为计算属性,被定义成方法格式。
//最后,这个方法必须要有一个返回值
rgb() {
return `rgb(${this.R},${this.G},${this.B})`
}
},
methods: {
show() {
console.log(this.rgb);
}
},
})
</script>
</body>
6.vue-cli(单页面应用程序)
单页面应用程序简称SPA,指的是一个web网站中只有唯一的一个html页面,所有的功能和交互都在这个唯一的页面中完成。
vue-cli是vue.js开发的标准工具,它简化了程序员基于webpack创建工程化的vue项目的过程,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。
1.vue-cli的安装
vue-cli是npm的一个全局包,使用npm install命令,既可以方便的安装在自己的电脑上。
命令:npm install -g @vue/cli
2.vue-cli创建项目
基于vue快速创建工程化的vue项目
在指定目录下输入npm create 项目名
项目名最好英文,否侧可能会出错。
3.vue项目目录的构成
- assets文件夹,存放项目中用到的静态资源文件,例如css样式表,图片等。
- components文件夹,程序员封装的,可复用的组件,都要放到components文件下。
- main.js是项目的入口文件,整个项目运行都先要执行main.js
- APP.vue是项目的跟组件。
组件化开发指的是:根据封装的思想,把页面上可重用的UI结构封装为组件,从而便捷了项目的开发和维护。
4.vue使用组件的步骤。
- 编写要使用的组件,在父组件中使用子组件,在父组件中使用import导入子组件,并在父组件中使用components注册后使用。
<template>
<div class="nihao">
<h1>app跟组件----{{message}}</h1>
<button @click="show">点击按钮</button>
<!-- 以标签形式使用注册的组件 -->
<Left></Left>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
export default {
components: {
'Left': Left //如果键和值一样既可以直接简写成一个
}
}
</script>
使用components注册的是私有子组件,如果多个组件要使用它则都要注册一次,显得很麻烦,所以可以注册全局组件。
5.全局组件注册
在vue项目的main.js入口文件中,使用Vue.component()方法注册。
//在main.js中导入要全局的组件。
import Right from '@/components/Right.vue'
//第一个参数是指定组件名称,
//第二个参数是导入的组件名称,最终使用的是参数一的名称
Vue.component("Myright", Right)
7.组件的自定义属性
props是组件的自定义属性,在封装通用组件的时候,使用props可以极大的提高组件的复用性。
例如我们在封装的组件中绑定的值是6,而在其他组件中使用这个封装的组件时,我不想使用它指定的值,我想自己给定初始值,这样可以对多个组件使用自己的初始值。
- 封装的组件创建自定义属性:指定为Right组件
<template>
<div>
这是一个right组件--{{aa}}
</div>
</template>
<script>
export default {
props: ['aa'] //aa现在的值是undefined
}
</script>
- 创建一个组件导入Right组件并且修改props指定的初始值.
<template>
<div>
<strong>这是一个left的组件</strong>
<!--使用组件,
将封装的组件中的aa初始化为33
-->
<Right aa="33"></Right>
</div>
</template>
<script>
import Right from '@/components/Right.vue' //导入组件
export default {
components: {
Right //组件注册
}
}
</script>
注意: 上面使用自定义属性初始的值默认是字符串,如果要想是数字则必须使用v-bind属性绑定,则aa就表示的是数字而并非字符串。
7.1props是是只读的
vue规定:组件中封装的自定义属性是只读的,程序员不能直接修改props的值,否则会直接报错。
如果要修改props的值,则必须将props的值转存到data中,data中的值是可读可写的。
<template>
<div>
这是一个right组件--{{conut}}
<button @click="conut+=1">+1</button>
</div>
</template>
<script>
export default {
props: ['aa'],
data () {
return {
conut: this.aa
}
},
}
</script>
7.2default默认值
在使用自定义属性时候,常常都是用户给定初始值,假如用户没有给定初始值,则自定义属性默认是undefined,这时候需要将自定义属性设置默认值,props则需要采用对象的形式书写。
<template>
<div>
这是一个right组件--{{conut}}
<button @click="conut+=1">+1</button>
</div>
</template>
<script>
export default {
props: {
aa: {
default: 12 //父组件的用户没有初始化,这是默认值就起作用了
}
},
data () {
return {
conut: this.aa
}
},
}
</script>
7.3type固定类型
在使用自定义属性的时候,我们不可能传入不同类型的值,例如布尔,数字,字符串等,这时候就需要type指定自定义属性接受的值,如果自定义属性接受的值不符合type指定的类型,则直接在终端报错。
<template>
<div>
这是一个right组件--{{conut}}
<button @click="conut+=1">+1</button>
</div>
</template>
<script>
export default {
props: {
aa: {
default: 12,
//父组件传入的值必须是Number类型,否则报错
type: Number
}
},
data () {
return {
conut: this.aa
}
},
}
</script>
7.4required必传设置
required属性接受布尔值,默认为false,如果required设置为true,则父组件必须给定自定义属性的初始化,否则控制台直接报错。
<template>
<div>
这是一个right组件--{{conut}}
<button @click="conut+=1">+1</button>
</div>
</template>
<script>
export default {
props: {
aa: {
default: 12,
//父组件传入的值必须是Number类型,否则报错
type: Number,
//父组件使用该组件时,必须传入自定义属性的值,否则控制台报错
required: true
}
},
data () {
return {
conut: this.aa
}
},
}
</script>
8.组件的生命周期
8.1生命周期&生命周期函数
-
生命周期是指一个组件从创建一>运行一>销毁整个阶段,强调的是一个时间段。
-
生命周期函数是由vue框架提供的内置函数,会伴随组件的生命周期,自动按次序执行。
**注意:**生命周期强调的是时间段,生命周期函数强调的是时间点
9.组件之间的数据共享
9.1父向子传值
父向子传值就使用自定义属性,并且父组件向子组件传入值大多都是为了展示数据,子组件创建自定义属性之后直接在html结构中引用展示,如果要修改props自定义属性的值,则必须使用data赋值后修改。
9.2子向父传值
子组件向父组件传值使用自定义事件,则要在子组件中使用 this.$emit()函数创建,第一个参数是要自定义事件的名字,第二个参数是要向父组件传入的数据。
- 子组件中定义自定义事件
<template>
<div>
<div>这是一个子组件{{message}} </div>
<button @click="show">子组件按钮</button>
</div>
</template>
<script>
export default {
data () {
return {
message: 0
}
},
methods: {
show () {
this.message++
//参数一numchange是自定义事件的名字
//参数二是将自增+1的值传入到父组件中
this.$emit("numchange", this.message)
}
},
}
</script>
- 父组件使用自定义事件获取子组件的数据。
<template>
<div class="nihao">
<h1>跟组件{{message}}</h1>
<hr>
<!--
numchange是子组件定义的事件,在父组件中可以直接使用,
getnewchange是父组件的事件函数名,子组件只要触发自定义事件,
则getnewchange也就会触发
-->
<Left @numchange="getnewchange"></Left>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
export default {
data () {
return {
message: 0
}
},
components: {
'Left': Left
},
methods: {
//getnewchange函数中默认的val就是子组件传入到父组件的数据,可以直接使用
getnewchange (val) {
this.message = val
}
},
}
</script>
9.3兄弟组件之间的数据共享
在vue2.x中,兄弟组件之间的共享方案是ventBus.兄弟组件之间传递数据,需要一个中间人,而这个就是一个js文件。
- 创建一个Eventbus.js的模块,并且向外共享一个vue实例对象。
- 在数据发送方,导入这个模块并且引用这个模块的$emit(“事件名”,要发送的数据)方法触发自定义事件。
- 在数据接收方,导入这个模块并且引用这个模块的$on(“事件名称”,事件处理函数)方法注册自定义事件,并且接受数据。
数据发送方:
<script>
import bus from '@/components/Eventbus.js'
export default {
data () {
return {
message: 0
}
},
methods: {
show () {
//数值+1
this.message++;
//通过事件将数值+1后直接使用自定义事件向外发出数据
bus.$emit("onon", this.message)
}
},
}
</script>
eventbus.js:
import Vue from 'vue'
//向外导出一个vue实例
export default new Vue()
数据接收方:
<script>
import bus from '@/components/Eventbus.js'
export default {
data () {
return {
conut: 0
}
},
//这个函数是vue自带的函数,在属性都创建好后就会触发
created () {
//通过数据发送方的onon事件接受数据并赋值给count
bus.$on('onon', val => {
this.conut = val
})
},
}
</script>
10.ref引用操作DOM元素
在vue项目中,强烈建议不要使用jQuery操作DOM,我们只需要维护好数据就行了,vue讲究的是数据驱动视图,如果实在要获取DOM元素,这时候就靠vue中的ref引用,ref可以不需要靠jQuery的情况下获取DOM元素或者组件的引用.
每个vue组件实例中,都包含一个 r e f s < / f o n t > 对象,里面都存储着对应的 D O M 元素和组件的引用,默认情况下 refs</font>对象,里面都存储着对应的DOM元素和组件的引用,默认情况下 refs</font>对象,里面都存储着对应的DOM元素和组件的引用,默认情况下refs指向的是一个空对象。
**注意:**ref的名字在当前组件实例中不能被冲突,有且只有唯一一个。
针对DOM元素的ref引用:
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
</h1>
<button @click="show">点击</button>
</div>
</template>
<script>
export default {
data () {
return {
message: 0
}
},
methods: {
//通过button的点击事件获取指定ref的dom元素
show () {
console.log(this.$refs.myh1);
}
},
}
</script>
针对组件的引用:
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
</h1>
<button @click="show">点击</button>
<hr>
<Left ref="left"></Left>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
export default {
data () {
return {
message: 0
}
},
components: {
Left,
},
methods: {
show () {
console.log(this);
//将left组件中的ref是left1的元素的字体颜色变为红色
this.$refs.left.$refs.left1.style.color = "red"
}
},
}
</script>
11.this.$nextTick(cb)
this.$nextTick表示cd回调推迟到下一个DOM元素更新周期之后执行,通俗的理解也就是,等DOM元素更新完成之后,在执行cb回调函数,从而操作最新的DOM元素。
如果不等元素更新完成直接操作dom,控制台直接会报undefined。
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
</h1>
<!--
blur表示失去焦点的事件
-->
<input type="text"
v-if="message"
@blur="showbutton"
ref="inref">
<button @click="show"
v-else>点击</button>
<hr>
</div>
</template>
<script>
export default {
data () {
return {
message: false //当message=true时input显示,否则button显示
}
},
methods: {
show () {
this.message = true
/*
当message=true时,这时候input元素还没有更新出来,
所以操作this.$refs.inref.focus();会显示为空,在最
外层包含$nextTick表示延迟执行,也就是等dom元素完全更新后
在执行。
*/
this.$nextTick(() => {
this.$refs.inref.focus();
})
},
//input失去焦点执行showbutton函数
showbutton () {
this.message = false
}
},
}
</script>
12.动态组件component
component是vue内置的一个标签,用来动态切换组件,该标签是一个占位符,没有什么实际意义。
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
</h1>
<hr>
<!-- is表示当前要展示的组件,这种是固定的 -->
<component is="Left"></component>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data () {
return {
message: 0
}
},
components: {
Left,
Right
},
}
</script>
上面的组件切换只有唯一的展示,要想动态切换必须配合v-bind和data属性使用
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
<button @click="message = 'Left'">组件一</button>
<button @click="message = 'Right'">组件二</button>
</h1>
<hr>
<!-- 通过v-bind绑定data中的属性,通过上述按钮动态切换
-->
<component :is="message"></component>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data () {
return {
message: ''
}
},
components: {
Left,
Right
},
}
</script>
12.1组件缓存keep-alive
keep-alive是vue内置的一个标签,主要就是与动态组件联合使用。
动态组件来回不停的切换,其原理是将组件在切换的过程中创建和销毁,默认情况下,销毁的组件中的数据也会被重置,如果还想要保留原来这个组件操作的数据,则需要在动态组件外部包一个keep-alive标签,该标签就会在动态组件跳转的过程中不会让其丢失数据。
<template>
<div class="nihao">
<h1 ref="myh1">
<p>跟组件</p>
<button @click="message = 'Left'">组件一</button>
<button @click="message = 'Right'">组件二</button>
</h1>
<hr>
<!--
keep-alive标签包含在外部,不能在内部,也只是个占位符,没有视觉效果
-->
<keep-alive>
<component :is="message"></component>
</keep-alive>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data () {
return {
message: 'Left'
}
},
components: {
Left,
Right
},
}
</script>
12.2keep-alive对应的生命周期
keep-alive在缓存的过程也有相应的生命周期函数,
- 组件被缓存时,会自动触发组件的deactivated生命周期函数。
- 组件被激活时,会自动触发组件的activated生命周期函数。
**注意:**当组件第一次被创建的时候,既会执行created生命周期函数,也会执行activated生命周期函数,而后面的每次组件的跳转只执行activated生命周期函数。
12.3keep-alive的include属性
include表示指定缓存的组件,如果有多个缓存组件,则用逗号分割。
<keep-alive include="Right">
<component :is="message"></component>
</keep-alive>
既然有指定要缓存的,也有指定不被缓存的。
<keep-alive exclude="Left">
<component :is="message"></component>
</keep-alive>
13.自定义指令
自定义指令分为私有自定义指令和全局自定义指令。
13.1私有自定义指令
在每个vue组件中,在data同等级别下创建directives节点,在此节点下声明私有自定义指令
<template>
<div class="nihao">
<h1 ref="myh1">
<p v-color>跟组件</p>
</h1>
<hr>
</div>
</template>
<script>
export default {
data () {
return {
message: ''
}
},
directives: {
//在创建自定义指令时,不加v-,但是再用的时候必须添加,指向的是一个配置对象
color: {
//当指令第一次被绑定到元素上的时候,会立即触发bind函数。
//形参中el表示的是当前指令所绑定到的哪个DOM对象。
bind (el) {
el.style.color = "blue"
}
}
}
}
</script>
13.2通过binding.value获取自定义指令绑定的值
在实际开发中,我们不可能固定的给元素传值,这时候需要给自定义属性传入不同的值。
- 可以引用data中的属性,既可以动态的改变。
- 或者直接在行内自定义指令中传值,这时候就要加上单引号,否则会认为是从data中获取的属性
<template>
<div class="nihao">
<h1 ref="myh1">
<p v-color="icon">跟组件</p>
</h1>
<hr>
</div>
</template>
<script>
export default {
data () {
return {
//这里的值是动态变化,只要icon发生变化,v-color就会传入不同的值
icon: 'red',
}
},
directives: {
color: {
//第二个参数binding就是v-color自定义属性传入的data属性
bind (el, binding) {
el.style.color = binding.value
console.log(binding);
}
}
}
}
</script>
13.3自定义指令的update函数
在自定义指令中,bind函数只在第一次被绑定到元素上时触发,如果data中的属性发生变化,bind函数也不会执行,这时候就需要update函数,在每次数据发生变化的时候就会触发自定义指令的update函数,update函数只能在directives节点下的自定义指令对象中。
<template>
<div class="nihao">
<h1 ref="myh1">
<button @click="icon = 'blue'">点击变色</button>
<p v-color="icon">跟组件</p>
</h1>
<hr>
</div>
</template>
<script>
export default {
data () {
return {
icon: 'red'
}
},
directives: {
color: {
//这个函数只会在第一次被绑定到元素中执行
bind (el, binding) {
el.style.color = binding.value
},
//这个函数会在数据更新后执行
update (el, binding) {
el.style.color = binding.value
},
}
}
}
</script>
13.4自定义指令的简写方式
如果bind和update函数中的逻辑完全一样,则对象格式的自定义指令可以简写成函数格式
<template>
<div class="nihao">
<h1 ref="myh1">
<button @click="icon = 'blue'">点击变色</button>
<p v-color="icon">跟组件</p>
</h1>
<hr>
</div>
</template>
<script>
export default {
data () {
return {
icon: 'red'
}
},
directives: {
//update和bind中的逻辑完全相同,直接写成函数格式
color (el, binding) {
el.style.color = binding.value
},
}
}
</script>
13.5全局自定义指令
全局自定义指令也就是在main.js中定义,使用Vue.directive()函数创建。
- 第一个参数是自定义指令的名字。
- 第二个参数是自定义指令的执行函数。
第一种方式:
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
})
第二种方式:
Vue.directive('color', (el, binding) => {
el.style.color = binding.value
})
14.前端路由
概念: Hash地址与组件之间的对应关系。
14.1vue-router路由
vue-router是vue.js官方给出的路由解决方案,它只能结合vue项目使用,能够轻松管理SPA项目中组件的切换。
vue-router路由下载,npm安装命令 npm install vue-router@3 -S
.
14.2创建路由模块
在src源代码目录下,新建一个router/index.js路由模块,初始化一下代码:
//导入vue和vue-router的模块
import Vue from 'vue'
import VueRouter from 'vue-router'
//调用Vue.use函数,将VueRouter安装为vue的插件
Vue.use(VueRouter)
//创建路由的实例对象
const router = new VueRouter()
//向外共享router的实例对象
export default router
在src的主入口main.js中导入router文件目录下的index.js文件,需要将导入的实例对象挂在上去。
import Vue from 'vue'
import App from './App.vue'
import router from '@/router/index.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router:router, //如果键和值一样,则可以简写一个router
}).$mount('#app')
14.3路由模块的基本使用
使用路由切换组件的显示需要vue-router内部的一个标签,router-view标签就是路由显示标签,结合锚点连接使用,要在哪里显示这个组件就将router-view这个标签放在哪,如果没有这个标签,这个组件就不会显示。
<template>
<div class="nihao">
<h3>跟组件</h3>
<!--
下面三个锚点连接点击后,下方的router-view标签将会替换成组件
-->
<a href="#/home">首页</a>
<a href="#/move">电影</a>
<a href="#/about">关于</a>
<hr>
<!-- 只要在项目中安装的vue-router,就可以使用,它只是个占位符,没有视觉效果 -->
<router-view />
</div>
</template>
<script>
export default {
}
</script>
最关键的一步就来了,就是创建hash地址和组件之间的对应关系,这时候就在router文件目录下的index.js中创建,需要在router实例对象中创建对应关系
//导入vue和vue-router的模块
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/components/Home.vue'
import Move from '@/components/Move.vue'
import About from '@/components/About.vue'
//调用Vue.use函数,将VueRouter安装为vue的插件
Vue.use(VueRouter)
//创建路由的实例对象
const router = new VueRouter({
//routes是一个数组,代表路由规则,作用:定义 hash地址 与 组件 之间的关系,
//path中的路径可以不需要在前面添加#,vue已经帮我们省略了
//component是锚点连接触发后跳转的组件
routes: [{
path: '/home',
component: Home
}, {
path: '/move',
component: Move
}, {
path: '/about',
component: About
}]
})
//向外共享router的实例对象
export default router
14.4router-link代替a标签的锚点
router-link也是vue-router的一个内置标签,它主要是代替a标签进行锚点点击.
- router-link本质上就是一个a标签,可以直接操作它。
- router-link中的to属性可以直接添加路径,不需要在前面添加#
<template>
<div class="nihao">
<h3>跟组件</h3>
<router-link to="/home">首页</router-link>
<router-link to="/move">电影</router-link>
<router-link to="/about">关于</router-link>
<hr>
<!-- 只要在项目中安装的vue-router,就可以使用,它只是个占位符,没有视觉效果 -->
<router-view />
</div>
</template>
<script>
export default {
}
</script>
14.5路由重定向
路由重定向指的是:用户在访问地址A的时候,强制用户跳转到地址C,从是展示特定的组件页面,通过路由规则的redirect属性,指定一个新的路由地址,而非组件名,从而很方便地设置路由的重定向。
//创建路由的实例对象
const router = new VueRouter({
//routes是一个数组,作用:定义 hash地址 与 组件 之间的关系
routes: [
//重定向的路由规则,在访问端口的时候直接跳转到/home的路径中,
//匹配到/home路径后就显示Home组件
{
path: '/',
redirect: '/home'
},
//路由规则
{
path: '/home',
component: Home
}, {
path: '/move',
component: Move
}, {
path: '/about',
component: About
}
]
})
14.6嵌套路由(子路由)
通过路由实现组件的嵌套展示,就是嵌套路由,声明子路由规则必须使用children属性申明子路由,子路由的规则path不要以/开头,直接写路径就行。
- 下方是about的组件
<template>
<div class="about">
<h1>about组件</h1>
<!-- 子路由连接 -->
<router-link to="/about/tab1">Tab1</router-link>
<router-link to="/about/tab2">Tab2</router-link>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
- 创建about组件的子路由规则
//导入vue和vue-router的模块
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/components/Home.vue'
import Move from '@/components/Move.vue'
import About from '@/components/About.vue'
import Tab1 from '@/components/Tabs/Tab1.vue'
import Tab2 from '@/components/Tabs/Tab2.vue'
//调用Vue.use函数,将VueRouter安装为vue的插件
Vue.use(VueRouter)
//创建路由的实例对象
const router = new VueRouter({
//routes是一个数组,作用:定义 hash地址 与 组件 之间的关系
routes: [
//重定向的路由规则
{
path: '/',
redirect: '/home'
},
//路由规则
{
path: '/home',
component: Home
}, {
path: '/move',
component: Move
}, {
path: '/about',
component: About,
//子路由规则,path的路径不要以/开头
children: [{
path: 'tab1',
component: Tab1,
},
{
path: 'tab2',
component: Tab2
}
]
}
]
})
//向外共享router的实例对象
export default router
14.7子路由的重定向和默认子路由
- 子路由的重定向与父路由的重定向查不多,只是最后重定向的路径必须父路径和子路径都要一起写。
//创建路由的实例对象
const router = new VueRouter({
//routes是一个数组,作用:定义 hash地址 与 组件 之间的关系
routes: [
//重定向的路由规则
{
path: '/',
redirect: '/home'
},
//路由规则
{
path: '/home',
component: Home
}, {
path: '/move',
component: Move
}, {
path: '/about',
component: About,
redirect: '/about/tab1',
//子路由规则,path的路径不要以/开头
children: [{
path: 'tab1',
component: Tab1,
},
{
path: 'tab2',
component: Tab2
}
]
},
]
})
- 默认子路由,也就是将子路由的其中一个path路径设置为空,当点击到父路由的时候会自动的展示子组件的,但是也要将点击的默认路由路径的子路径删除,只填写父路径即可。
//创建路由的实例对象
const router = new VueRouter({
//routes是一个数组,作用:定义 hash地址 与 组件 之间的关系
routes: [
//重定向的路由规则
{
path: '/',
redirect: '/home'
},
//路由规则
{
path: '/home',
component: Home
}, {
path: '/move',
component: Move
}, {
path: '/about',
component: About,
// redirect: '/about/tab2',
//子路由规则,path的路径不要以/开头
children: [{
path: 'tab1',
component: Tab1,
},
{
path: '', //设置为空,当访问about路径是也自动展示
component: Tab2
}
]
},
]
})
但是点击的路由路径也要少一层。
<router-link to="/about/tab1">Tab1</router-link>
<router-link to="/about">Tab2</router-link>
14.8动态路由匹配
动态路由指的是:把Hash地址中的可变部分定义为参数项,从而提高路由规则的复用性。
在vue-router中使用英文的冒号(:)来定义路由的参数项.
**作用:**如果传来的路由前面一个地址一样,而后面却不一样,这时候需要在创建一个路由进行匹配,就会导致路由的匹配越来越多,就需要用到动态路由,使路由自动匹配。
- 例如下方的路由路径,就会重复,可以使用到动态路由
<!--
move路径都是一样,所以可以共用move路由,
后面指定的电影名却不一样,这时候就需要使用动态匹配路由
-->
<router-link to="/move/复联1">电影</router-link>
<router-link to="/move/复联2">电影</router-link>
<router-link to="/move/变形金刚3">电影</router-link>
- 需要在路由匹配的时候冒号id的类型,就可以动态的实现组件之间不同的显示效果
{
path: '/move/:id', //id只是个名字而已,不是固定的值,:代表这个id是动态路由匹配,
//我们可以在上面三个不同的路由组件中显示不同的电影。
component: Move
}
- 动态路由匹配的值会存到组件中的this.$route.params对象中
<template>
<div class="move">
<!-- 通过this.$route.params.id获取到路由后面不同的值 -->
<h1>Move组件----{{this.$route.params.id}}</h1>
</div>
</template>
<script>
export default {
created () {
console.log(this);
},
}
</script>
14.7动态路由的第二种接收匹配的路径(props形式传参)
在上面使用的是this.$route.params.mid接收,如果很多要进行传值,这时候就显得麻烦,我们就在路由匹配规则中开启props传值,这样就直接使用自定义属性接收并渲染。
两种方式都是独立存在的,都可以一起使用
- 路由规则设置
{
path: '/move/:mid',
component: Move,
props: true //开启props传值
}
- 组件接收props的值
<template>
<div class="move">
<h1>Move组件----{{this.$route.params.mid}}---{{mid}}</h1>
</div>
</template>
<script>
export default {
props: ['mid'], //接收来自路由规则的动态传值
created () {
console.log(this);
console.log(this.mid)
},
}
</script>
14.8路由的注意事项
- 在hash地址中,/后面的参数项叫做路径参数,使用this.$route.params获取路径参数
- 在hash地址中,?后面的参数项叫做查询参数,使用this.$route.query访问查询参数,
- 在this.$route中,path代表的路径部分,fullPath是完整的地址。
上述中如果获取的路径是乱码,则要使用decodeURI()函数转换。
14.9声明式导航&编程式导航(重点)
- 在浏览器中,点击连接实现导航的方式叫做明式导航,例如:普通网页点击a标签,vue项目中点击router-link标签都数语声明式导航。
- 在浏览器中,调用api方法实现导航的方式,叫做编程式导航,例如:普通网页调用location.href跳转到新页面的方式,属于编程式导航。
14.10vue-router的编程式导航
vue-router提供的许多编程式导航的API,其中最常见的API是:
- this.$router.push(“hash地址”),
跳转到指定的hash地址,并且增加一条历史记录,也就是说我们访问后的网页都可以后退和前进
- this.$router.replace(“hash地址”),
跳转到指定的hash地址,替换掉当前的历史记录
- this.$router.go(数值n),
后退和前进到指定的页面,-1代表后退一层,1代表前进一层,如果go方法超过指定的层数将不会跳转,只留在本页面
this.$router.go()的简写形式
在实际开发中,一般只会前进和后退一个页面,因此vue-router提供了一下两个方法:
- this.$router.back(),在历史记录中后退到上一个页面
- this.$router.forward(),在历史记录中前进到下一个页面
**注意:**在行内使用跳转的方式时,要将this省略
14.11Vue的query传参
https://blog.csdn.net/weixin_49832841/article/details/124228972(案例)
query传参也就是在?后面进行key/value赋值。首先创建一组点击按钮,实现不同数据传递.
- home.vue
<template>
<div>
<h1>ahome组件</h1>
<ul>
<li v-for="item in mes" :key="item.id">
<!-- <RouterLink to="/home/tab?id=1&message=你好">{{item.message}}</RouterLink> 这个只能传固定的-->
<!-- <router-link :to="`/home/tab?id=${item.id}&message=${item.message}`" >{{item.message}}</router-link> 这个利用模板字符串传递不同的 -->
<router-link :to="{
path:'/home/tab',
query:{
id:item.id,
message:item.message
}
}">
{{item.message}}
</router-link>
</li>
</ul>
<router-view/>
</div>
</template>
<script>
export default {
name: "ToHome",
data(){
return{
mes:[
{id:1,message:'服务'},
{id:2,message: '实战'},
{id:3,message: '简介'},
{id:4,message: '推送'}
]
}
}
}
</script>
- Datail.vue
<template>
<ul>
<li>id:{{$route.query.id}}</li>
<li>message:{{$route.query.message}}</li>
</ul>
</template>
<script>
export default {
name: "dataIl"
}
</script>
<style scoped>
</style>
14.12命名路由
命名路由就是使用name去指向这个路由,不需要path,一般用于多级(超过三层)的时候使用
- router.js
routes:[
{
path:'/',
redirect:'/home'
},
{
path:'/home',
component: toHome,
children:[
{
name:'tab', //设置当前路由名字
path:'tab',
component:DataIl
}
]
},
{
path:'/about',
component:toAbout
}
]
- main.vue
<template>
<div>
<h1>ahome组件</h1>
<ul>
<li v-for="item in mes" :key="item.id">
<!-- <RouterLink to="/home/tab?id=1&message=你好">{{item.message}}</RouterLink> 这个只能传固定的-->
<!-- <router-link :to="`/home/tab?id=${item.id}&message=${item.message}`" >{{item.message}}</router-link> 这个利用模板字符串传递不同的 -->
<router-link :to="{
name:'tab', //再用命名路由跳转
//path:'/home/tab',
query:{
id:item.id,
message:item.message
}
}">
{{item.message}}
</router-link>
</li>
</ul>
<router-view/>
</div>
</template>
14.13路由的params参数传递
params参数传递是通过/占位符/占位符形式传递,如果使用对象形式传递,这时不能使用path跳转,必须使用name
- router.js
routes:[
{
path:'/',
redirect:'/home'
},
{
path:'/home',
component: toHome,
children:[
{
name:'tab',
path:'tab/:id/:message', //使用占位符占位
component:DataIl
}
]
},
{
path:'/about',
component:toAbout
}
]
- main.vue
<template>
<div>
<h1>ahome组件</h1>
<ul>
<li v-for="item in mes" :key="item.id">
<!-- <router-link :to="`/home/tab/${item.id}/${item.message}`" >{{item.message}}</router-link> 这个利用模板字符串传递不同的-->
<router-link :to="{
name:'tab',
// path:'/home/tab', 不能使用path传递,
params:{
id:item.id,
message:item.message
}
}">
{{item.message}}
</router-link>
</li>
</ul>
<router-view/>
</div>
</template>
- tab.vue
<template>
<ul>
<li>id:{{$route.params.id}}</li>
<li>message:{{$route.params.message}}</li>
</ul>
</template>
15.导航守卫
导航守卫可以控制路由的访问权限,
15.1全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫,因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制,我们也需要通过vue-router路由来实现
//创建路由的实例对象
const router = new VueRouter({......})
//调用路由实例对象的beforeEach方法,既可以声明全局前置守卫
//每次发生路由导航跳转的时候,都会触发beforeEach里面的回调函数
router.beforeEach()
守卫方法中回调函数的三个形参:
1. to 是将要访问的路由的信息对象。
2. from 是将要离开的路由的信息对象
3. next 是一个函数,调用next()表示放行,允许这次路由导航
next()函数的三种调用方式:
- 当前用户拥有后台主页的访问权限,直接放行,next()
- 当前用户没有后台主页的访问权限,强制跳转到登录页面,next(‘/login’)
- 当前用户没有后台主页的访问权限,不允许跳转到后台主页,next(false)
简单例子:
router.beforeEach((to, from, next) => {
if (to.path === '/main') {
const token = localStorage.getItem('token'); //获取token
if (token) { //如果有token则t
next()
} else {
next('/login')
}
} else {
next()
}
})
16.mixin混入
mixin混入是指将不同组件中的相同的方法和属性封装在一个js文件中,这时候不同组件中可以引入这个文件,放入mixins数组中就可以使用,
export let a={
data(){
return{
name:"hello" //如果在当前vc中有这个属性,那么会使用vc中的属性,不会使用这个文件中的属性
}
},
methods:{
show(){
this.name="我的世界"
}
}
}
<template>
<div>
<h1>{{name}}</h1>
<button @click="show">按钮</button>
</div>
</template>
<script>
import {a} from '@/mixin/mixin'
export default {
name: "HelloWorld",
mixins:[a]
}
</script>
**注意:**上面vue文件中虽然没有定义data方法中的name和方法,但是在mixin.js文件中定义了,如果没有引入这个文件,也会报错,解决办法就是引入这个文件,并且使用mixins数组接收这个。
合并生命钩子函数:如果在v多个ue组件中使用生命钩子函数,这时候函数中的方法一样或者要处理一样的需求,这时候也需要使用混入文件,这时候如果在vue组件中和混入文件都有这个生命钩子函数,他们会合并一起,首先执行的是自己的生命钩子函数。
17.全局事件总线
全局事件总线是一种组件间通信的方式,适用于任意组件间通信
全局事件总线并不是插件,配置文件等等,事件总线是程序员在做Vue开发中总结积累的一套方法,是一套规则,只要满足这套规则,就可以实现组件间的通信。
- 全局事件总线首先在vm中设置,也就是在Main.js中中设置
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus=this //这个$bus就代表当前的这个VM,
}
}).$mount("#app")
- 事件触发
<template>
<div>
<button @click="show">按钮</button>
</div>
</template>
<script>
export default {
name: "TableItem",
data(){
return{
name:"hello world"
}
},
methods:{
show(){
this.$bus.$emit("bu",this.name) //设置事件触发的事件名 bu ,将name传入到这个事件中去
}
}
}
</script>
<style scoped>
</style>
- 事件接收
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h1>{{name}}</h1>
<table-item></table-item>
</div>
</template>
<script>
import TableItem from "@/components/TableItem";
export default {
name: 'App',
data(){
return{
name:"你好"
}
},
components:{
TableItem
},
mounted() {
this.$bus.$on("bu",(a)=>{ //事件接收,data就是传入进来的name,这里可以写箭头函数,执行的是这个vc
this.name=a
})
},
beforeDestroy() {
this.$bus.$off("bu") //养成良好习惯,在组件销毁时销魂这个事件
}
}
</script>
<style>a
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
18.消息订阅与发布
消息订阅与发布类似与全局事件总线,下面使用的是一个外部的一个插件, pubsub-js插件,就可以订阅与发布
导入这个插件
npm i pubsub-js
- 数据接收方
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h1>{{name}}</h1>
<table-item></table-item>
</div>
</template>
<script>
import pubsub from 'pubsub-js' //导入插件
import TableItem from "@/components/TableItem";
export default {
name: 'App',
data(){
return{
name:"你好"
}
},
components:{
TableItem
},
methods:{
demo(a){
this.name=a
}
},
mounted() {
// this.$bus.$on("bu",this.demo)
this.ppid= pubsub.subscribe('fun',(msgName,data)=>{ //数据接收方,参数一是消息名,参数是二是消息体
this.name=data
})
},
beforeDestroy() {
// this.$bus.$off("bu")
//销魂这个消息,接受的是这个消息id销魂
pubsub.unsubscribe(this.ppid)
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- 数据发送方
<template>
<div>
<input type="text" name="nn" v-model="name" @keyup="show">
<button @click="show">按钮</button>
</div>
</template>
<script>
import pubsub from "pubsub-js"; //都凹入这个插件
export default {
name: "TableItem",
data(){
return{
name:"我"
}
},
methods:{
show(){
// this.$bus.$emit("bu",this.name) //设置事件触发的事件名 bu ,将name
//数据发送方,this.name是值
pubsub.publish("fun",this.name)
}
}
}
</script>
<style scoped>
</style>
19.过度与动画
动画过度插件:https://animate.style/(非常牛皮)
20.vuex状态管理
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
首先下载使用npm导入vuex:
npm i vuex@3 //如果使用的是vue2的版本,则需要导入vuex的三版本,如果是vue3则导入vuex的四版本
- 配置vuex的store
import Vue from 'vue'
import Vuex from 'vuex'
//将Vuex作为vue的配置使用
Vue.use(Vuex)
//执行动作
const actions={}
//数据操作
const mutations={}
//状态和数据
const state={}
//默认导出
export default new Vuex.Store({
actions,
mutations,
state
})
- main.js中装配
import Vue from 'vue'
import App from './App.vue'
//导入那个vuex的配置
import store from "@/store";
Vue.config.productionTip = false
new Vue({
store, //直接放入到vue中
render: h => h(App),
}).$mount("#app")
1.实例(实现加减)
- app.vue
<template>
<div id="app">
<!--这里直接访问-->
<h1>最终结果{{$store.state.sum}}</h1>
<button @click="add">+</button>
<button @click="sub"></button>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
}
},
methods:{
add(){
//实现动作的执行
this.$store.dispatch("add",1);
//this.$store.commit("add",1) //如果没有动作上的需求可以直接实现数据的修改
},
sub(){
this.$store.dispatch("sub",1)
}
},
}
</script>
- index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//执行动作
const actions={
add(context,value){
context.commit("add",value)
},
sub(context,value){
context.commit("sub",value)
}
}
//数据操作
const mutations={
add(state,value){
state.sum+=value
},
sub(state,value){
state.sum-=value
}
}
//状态和数据
const state={
sum:0
}
export default new Vuex.Store({
actions,
mutations,
state
})
2.getters配置
getters配置是根据state中的数据在进行一次加工,类似于计算属性,然后将新数据进行return
- index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//执行动作
const actions={
add(context,value){
context.commit("add",value)
},
sub(context,value){
context.commit("sub",value)
}
}
//数据操作
const mutations= {
add(state, value) {
state.sum += value
},
sub(state, value) {
state.sum -= value
}
}
//getters配置
const getters={
getSum(state){
return state.sum*10
}
}
//状态和数据
const state={
sum:0
}
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
- main.vue
<template>
<div id="app">
<h1>最终结果{{$store.state.sum}}---{{$store.getters.getSum}}</h1>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
}
},
methods:{
add(){
// this.$store.dispatch("add",1);
this.$store.commit("add",1)
},
sub(){
this.$store.dispatch("sub",1)
}
},
}
</script>
3.mapState配置
如果在state中存储着许多的数据,但在一个vue组件中全都需要用,这时候往往就会产生很多的$store.state.,例如下面
- index.js
//状态和数据
const state={
sum:0,
title:'学习Java',
message:'学习vue'
}
- main.vue
<template>
<div id="app">
<!--这时候就产生大量冗余 ,反复出现-->
<h1>最终结果{{$store.state.sum}}</h1>
<h1>主题:{{$store.state.title}}</h1>
<h1>介绍:{{$store.state.message}}</h1>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
}
},
methods:{
add(){
// this.$store.dispatch("add",1);
this.$store.commit("add",1)
},
sub(){
this.$store.dispatch("sub",1)
}
},
}
</script>
**注意:**上面出现大量的重复state数据,这时候我们就用到vuex中的一个方法自动生成,其实相当于计算属性,下面进行修改
- main.vue
<template>
<div id="app">
<h1>最终结果{{$store.state.sum}}</h1>
<h1>主题:{{getTitle}}</h1>
<h1>介绍:{{getMessage}}</h1>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
import {mapState} from "vuex"
export default {
name: 'App',
data(){
return{
}
},
computed:{
//es6语法,在对象中添加对象,必须使用...OBject
//getTitle相当于计算属性的方法,可以直接用
...mapState({getTitle:'title',getMessage:'message'})
},
methods:{
add(){
// this.$store.dispatch("add",1);
this.$store.commit("add",1)
},
sub(){
this.$store.dispatch("sub",1)
}
},
mounted() {
//打印这个mapState
console.log(mapState({getTitle:'title',getMessage:'message'}))
}
}
</script>
**修改:**关于上面的mapState配置,如果键值对的名字一样,直接可以写成数组形式,里面用字符串包裹
computed:{
//es6语法,在对象中添加对象,必须使用...OBject
// ...mapState({getTitle:'title',getMessage:'message'})
...mapState(['title','message'])
},
4.mapGetters配置
针对上面的mapState配置可以仿照结构设置getters配置,例如下面:
computed:{
//es6语法,在对象中添加对象,必须使用...OBject
// ...mapState({getTitle:'title',getMessage:'message'})
...mapState(['title','message']),
// ...mapGetters({getSum:'getSum'})
...mapGetters(["getSum"])
},
5.mapActions配置
mapActions配置也可以仿照上面的mapState和mapGetters配置改变。最主要的区别就是还需要传入值,mapActions是与actions对话的函数,就是this.$store.dispatch()函数。
<template>
<div id="app">
<h1>最终结果{{$store.state.sum}}---{{getSum}}</h1>
<h1>主题:{{title}}</h1>
<h1>介绍:{{message}}</h1>
<!-- 这时add方法里面必须传入值-->
<button @click="add(n)">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions} from "vuex"
export default {
name: 'App',
data(){
return{
n:2
}
},
computed:{
//es6语法,在对象中添加对象,必须使用...OBject
// ...mapState({getTitle:'title',getMessage:'message'})
...mapState(['title','message']),
// ...mapGetters({getSum:'getSum'})
...mapGetters(["getSum"])
},
methods:{
// add(){
// // this.$store.dispatch("add",1);
// this.$store.dispatch("add",1)
// },
sub(){
this.$store.commit("sub",1)
},
//使用键值对形式
// ...mapActions({add:'add'}),
//使用数组形式,及键值对相同
...mapActions(['add'])
},
mounted() {
//打印这个mapState
console.log(mapState({getTitle:'title',getMessage:'message'}))
}
}
</script>
6.mapMutations配置
mapActions配置也可以仿照上面的mapState和mapGetters配置改变。最主要的区别就是还需要传入值,mapActions是与mutations对话的函数,就是this.$store.commit()函数。
<template>
<div id="app">
<h1>最终结果{{$store.state.sum}}---{{getSum}}</h1>
<h1>主题:{{title}}</h1>
<h1>介绍:{{message}}</h1>
<!-- 这时add里面必须传入值-->
<button @click="add(n)">+</button>
<button @click="sub(n)">-</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions,mapMutations} from "vuex"
export default {
name: 'App',
data(){
return{
n:2
}
},
computed:{
//es6语法,在对象中添加对象,必须使用...OBject
// ...mapState({getTitle:'title',getMessage:'message'})
...mapState(['title','message']),
// ...mapGetters({getSum:'getSum'})
...mapGetters(["getSum"])
},
methods:{
// add(){
// // this.$store.dispatch("add",1);
// this.$store.dispatch("add",1)
// },
// sub(){
// this.$store.commit("sub",1)
// },
//使用键值对形式
// ...mapActions({add:'add'}),
//使用数组形式,及键值对相同
...mapActions(['add']),
//使用键值对传递
// ...mapMutations({sub:'sub'}),
//使用数组形式
...mapMutations(['sub'])
},
mounted() {
//打印这个mapState
console.log(mapState({getTitle:'title',getMessage:'message'}))
}
}
</script>
7.vuex模块化
上面6种都是基于一个模块进行数据的改变和交互,实现起来非常简单,但是如果碰到多个模块或者多个不同的处理,下面就需要使用到vuex模块进行整理,通过实现两个不同的服务,第一个是实现加减乘除,第二个实现列表列表增加。
- 修改store的配置,从中分开两个文件。
import Vue from 'vue'
import Vuex from 'vuex'
import list from "@/store/list"; //列表添加
import mathematics from "@/store/mathematics"; //加减乘除运算
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
list:list, //如果键值对一样只需要写一个
mathematics:mathematics
}
})
- 创建List.js实现
import {nanoid} from "nanoid";
export default {
namespaced:true, //开启模块化的设置
actions:{
},
mutations: {
addUser(state,value){
let User={
id:nanoid(),
name:value
}
state.userList.unshift(User)
},
remove(state){
state.userList.pop()
}
},
getters:{
},
state:{
userList:[
{id:1,name:'user'}
]
}
}
3,. mathematics.js实现
export default {
namespaced:true,
actions:{
},
mutations: {
add(state,value){
state.sum+=value
},
sub(state,value){
state.sum-=value
}
},
getters:{
bigSum(state){
return state.sum*2
}
},
state:{
sum:23
}
}
- 实现List.vue
<template>
<div class="root">
<input type="text" v-model="message"/>
<button @click="addUser(message)">添加</button>
<button @click="remove">删除</button>
<ul>
<li v-for="item in userList" :key="item.id">{{item.name}}</li>
</ul>
</div>
</template>
<script>
import {mapState,mapMutations} from 'vuex'
export default {
name: "ToList",
data(){
return{
message:'',
}
},
computed:{
...mapState('list',["userList"])
},
methods:{
...mapMutations('list',['addUser','remove'])
},
mounted() {
console.log(this)
}
}
</script>
<style scoped>
.root{
width: 500px;
height: 200px;
background-color: #a3faee;
}
</style>
- MathEmatics.vue实现
<template>
<div class="root">
<h2>总数--{{sum}}</h2>
<h2>读取总数的两倍--{{bigSum}}</h2>
<div>
<select v-model="index" >
<option :value="1" selected="selected">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="add(index)">add</button>
<button @click="sub(index)">sub</button>
</div>
</div>
</template>
<script>
export default {
name: "MathEmatics",
data(){
return{
index:1
}
},
computed:{
sum(){
return this.$store.state.mathematics.sum
},
bigSum(){
return this.$store.getters["mathematics/bigSum"]
}
},
methods:{
add(value){
this.$store.commit('mathematics/add',value)
},
sub(value){
this.$store.commit('mathematics/sub',value)
}
},
mounted() {
// console.log('数学',this)
}
}
</script>
<style scoped>
.root{
width: 300px;
height: 200px;
background-color: #a3faee;
}
</style>