[Vue]--基础增强

前面已经写过一篇vue的基础知识和用法,现在因为在工作中需要使用到vue全家桶,自己来了一个系统的学习,在这片博文中,补充一下前面所漏掉的知识。
Vue基础用法


defineProperty

基础内容

Object.defineProperty是es5新加的给对象属性设置描述符的方法,所以Vue不支持ie8及其以下的浏览器。

1
Object.defineProperty(obj, prop, descriptor)  // 对象、属性、描述符

基本的描述符有3个:
writable:true/false — 是否可写:可以写的话就是可以修改
configurable:true/false — 是否可配置:可以配置的话,就可以删除属性。
enumerable:true/false — 是否为可枚举的:可以枚举就可以遍历循环

除了上面三个描述符意外,还可以定义value值和getter/setter方法。
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性

实现双向数据绑定

通过定义getter/setter方法,和事件监听就可以做到Vue中最大的一个特写,数据双向绑定。
html的body中:

1
<input type="text" id="input"/>

在html页面引入的js文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 引入js约束 -->
<script src="node_modules/vue/dist/vue.js"></script>
<script>
let obj = {}; // 定义一个对象

let temp = {}; // 做中间变量
Object.defineProperty(obj,'name',{
get(){ // 取obj的name属性会触发get方法
return temp['name']; // 将值存到中间变量中
},
set(val){ // 给obj的name属性赋值会触发set会触发set方法 val是被赋予的值
temp["name"] = val; // 改变中间变量 temp的这结果
input.value = val; // 将值赋予输入框
}
})
input.value = obj.name; // 页面一加载 会调用get方法
input.addEventListener('input',function () { // 等待输入框的变化
obj.name = this.value; // 当值变化时会调用set方法
})
</script>

  1. 第一次加载页面的时候,input.value = obj.name; 输入框的值会等于obj.name属性的值。
  2. 修改输入框的值,会触发事件,obj.name = this.value;此时obj的name属性会等于输入框input的值
  3. 在控制台修改obj.name值,会调用set方法,temp["name"] = val;input.value = val;此时会将中间变量的name属性赋值为val,以及输入框的值设置为val,这样输入框input的值也就是obj.name的值了。
  4. 在读取obj.name的值的时候,会调用get方法,return temp['name']返回的就是中间变量的值。

通过上面四步,就实现了数据的双向绑定了!


js中对数组操作方法

变异方法

js中能改变数组的方法叫做变异方法,就是能该表调用这些方法的数组的原始值。
方法总共有7个:

  1. push():往数组最后面添加一个元素,成功返回当前数组的长度
  2. pop():删除数组的最后一个元素,成功返回删除元素的值
  3. shift():删除数组的第一个元素,成功返回删除元素的值
  4. unshift():往数组最前面添加一个元素,成功返回当前数组的长度
  5. splice():有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除后想要在原位置替换的值(可选)
  6. sort():使数组按照字符编码默认从小到大排序,成功返回排序后的数组
  7. reverse():将数组倒序,成功返回倒序后的数组

非变异方法

这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组

  1. filter():创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。filter中传入的是一个函数,(item)=>{ return true/false},item指的时数组中的每一项,返回为true的时候会保留,返回false则去除这项,最后生成新的数组。
  2. concat():用于连接两个或多个数组。
  3. slice():可从已有的数组中返回选定的元素。 可提取字符串的某个部分,并以新的字符串返回被提取的部分。array.slice(start, end)第一个参数必填,第二从参数选填。
  4. map():方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。所以其传入的也是一个函数 如:(item)=>item*10
  5. foreach():forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组);匿名函数中的this都是指向window;只能遍历数组。与map不同的是,并不会返回一个新的数组,forEach()默认遍历谁就返回谁。


双向绑定中的数组操作

切记不可以通过操作数组索引来修改数据,例如vm.arr[0] = 100 或者 vm.arr.length -= 1这种操作并不会被监听到,并不会使数据重新渲染。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" >
{{arr[0]}}
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
//Vue会循环data中的数据 (数据劫持) 一次的增加getter和setter方法
let vm = new Vue({
el:"#app",
data:{
arr:[1,2]
}
});
</script>
</body>
</html>

可以知道。初始化页面中显示的是 arr[0],也就是1,
当我们在控制台修改它的值的时候:

这时会出现一个错觉,数据的双向绑定失效了吗?其实是这种修改并没有被监听到。个人理解,如果数组的地址没有改变(重新赋一个数组),或者使用了变异函数,那么就不会被监听到
如下两种操作,就可以被监听到:


v-bind 绑定方法的传参问题

在vue中都是通过 v-bind(语法糖:@)来给事件绑定方法,那么所绑定的方法要不要加上()呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--如果不传递参数,则不要写括号。如果写括号了,要手动传入$event属性?-->
<div id="app1" @click="fn">
点击2
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:"#app1",
methods:{
//method 和data 全部都放在vm上,而且名字不能冲突,冲突会报错
//methods中的this指向的都是实例
fn(event){
alert(event);
}
},
data:{
a:1
}
})
</script>
</body>
</html>

可以看到fn方法写了一个形参,然而绑定的时候,我们并没有给fn传递参数,但是在当触发点击2的时候

可以看到,方法默认的把触发事件 MouseEvent给传递过来了。

假如事件绑定的时候,带了括号呢?这里不做过多的演示了,直接说出结论:如果写了括号,并且没有传递参数,那么弹出的将会是undefined,也就是说什么参数都没传,如果想把触发事件传递过来,需要自己手动加上$event。


checkbox和radio

单选框和复选框, 如何绑定数据呢?绑定啥数据呢?

checkbox

单复选框只有一个选项时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<meta name="google" content="notranslate" />
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" >
<input type="checkbox" v-model="c" >
{{c}}
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
c:100
}
})
</script>
</body>
</html>

当还未点击复选框的时候:

当点击了复选框之后:

当点击了复选框之后:

可以得出结论,复选框,如果只有一个选项的时候,并且未指定value值的时候,他的值是一个Boolean类型的。选中为true不选中为false

那么如果设置value值,该用什么数据类型来绑定呢?可能大家都想到了,要用数组来接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html lang="en">
<meta name="google" content="notranslate" />
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" >
<input type="checkbox" v-model="c" value="苹果" >
<input type="checkbox" v-model="c" value="西瓜" >
<input type="checkbox" v-model="c" value="香蕉" >
<input type="checkbox" v-model="c" >
{{c}}
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
c:[],
}
})
</script>
</body>
</html>

结果如下:

radio

radio如果没有设置value值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html lang="en">
<meta name="google" content="notranslate" />
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" >
<input type="radio" v-model="a" >
<input type="radio" v-model="a" >
<input type="radio" v-model="a" >
{{a}}
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
a:100,

}
})
</script>
</body>
</html>


可以看到程序出问题了,选中一个之后所有的都被选中,并且不可更改了,所以想要radio正常工作,必须要给radio指定value值。

如果设置了value值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html lang="en">
<meta name="google" content="notranslate" />
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" >
<input type="radio" v-model="a" value="1">
<input type="radio" v-model="a" value="2">
<input type="radio" v-model="a" value="3">
{{a}}
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
a:100,

}
})
</script>
</body>
</html>


加上value属性,radio才正常工作!


修饰符modifier

事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • stop:阻止事件继续传播 即阻止它的捕获和冒泡过程
  • prevent:阻止默认事件发生 即event.preventdefault():
  • capture:添加事件监听器时使用事件捕获模式,即在捕获模式下触发
  • self:只当在 event.target 是当前元素自身时触发处理函数
  • once:只执行一次

修饰符可以串联,并且串联的顺序很重要

按键修饰符

在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

1
2
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:

1
2
3
4
5
<!-- 同上 -->
<input v-on:keyup.enter="submit">

<!-- 缩写语法 -->
<input @keyup.enter="submit">

全部的按键别名:

  • enter
  • tab
  • delete (捕获“删除”和“退格”键)
  • esc
  • space
  • up
  • down
  • left
  • right

可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

1
2
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • ctrl
  • alt
  • shift
  • meta

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17。

鼠标按钮修饰符

  • left
  • right
  • middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。


自定义指令directive

v-if、v-bind这些都是vue中自带的一些指令,在需要的时候,我们可以自定义指令来满足我们的需求。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate"/>
<title>nav_test</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css">
<style>
.a {
position: absolute;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id="app">
<button v-color="flag">变色</button>
<div v-drag="" class="a"></div>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script src="../node_modules/axios/dist/axios.js"></script>
<script>
let vm = new Vue({
el: '#app',
directives: {
color(el, bindings) { // el指代的是当前元素
// 添加一个背景颜色,颜色是传进来的。
el.style.backgroundColor = bindings.value;
},
drag(el) {
// 添加一个mousedown事件
el.onmousedown = function (e) {
var disx = e.pageX - el.offsetLeft;
var disx = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
el.style.left = e.pageX - disx + 'px';
el.style.top = e.pageY - disx + 'px';
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
}
el.preventDefault(); // 不执行默认行为 如a的跳转
}
}
},
data: {
// 设置v-color的背景颜色为蓝色
flag: 'blue',
},

})
</script>
</html>

可以看到自定义指令的作用:


实例生命周期钩子

生命周期图:

如果不太懂vue的生命周期,我相信看了这个图后大家就会懂了,生命周期方法总共有8个


组件component

根据功能划分

  1. 页面级组件:一个页面是一个组件
  2. 基础组件:将可复用的部分抽离出来

好处:

  1. 提供开发效率
  2. 方便重复使用
  3. 便于协同开发
  4. 更容易维护

一个自定义标签,vue就会把它看成一个组件,vue可以给标签赋予一定意义

根据用法划分:

  1. 全局组件:可以声明一次在任何地方使用(一般写插件的时候全局组件多一些)
  2. 局部组件:必须告诉这个组件属于谁

组建的命名:

  1. 组件名不要带有大写 多个单词用 - 隔开
  2. 只要组件名和定义名字相同时可以的(首字母可以大写)
  3. html采用短横线隔开法 js中转驼峰也是可以的

全局组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面级组件-一个页面就是一个组件</title>
</head>
<body>
<div id="app">
<my-handsome></my-handsome>
<div>我帅的呀批</div>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('my-handsome', {
// 一个对象可以看出一个组件
template:'<div>{{msg}}</div>', // 只能是一个根元素
data(){ // 组件中的数据必须为函数类型,返回一个实例作为组件的数据
return {msg:'我很英俊!'}
}
})
let vm = new Vue({
el:'#app',
data:{
msg:'hello',
arr:[1,2,3],
},
})
</script>
</body>
</html>

可以看到:

局部组件

局部组件使用的三部曲:
1.创建组件 2.引用组件 3.使用组件
组件是相互独立的 不能夸作用域 实例也是一个组件,有生命周期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>局部组件</title>
</head>
<body>
<div id="app">
// 使用组件
<component1></component1>
<component2></component2>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let obj = {school: 'aowin'};// 如果组件共用了数据,会导致同时更新。
// 子组件不能直接使用父组件的数据(组件之间的数据交互)
// 组件理论上可以无线嵌套

//创建组件
let component1 = {
template: '<div @click="fn">我是一个组件</div>',
methods: {
fn() {
alert("component1");
}
}
};
let component2 = {
template: '<div>组件2</div>'
}
let vm = new Vue({
el: '#app',
components: {
// 注册/引用 组件
component1,
component2
},
data: {
a: 1,
}
})
</script>
</body>
</html>

组件嵌套

如果要在一个组件中 使用另一个组件 先要保证使用的组件是真实存在的
然后通过components注册你要使用的组件,组件需要在父级的模板中通过标签的形式引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>局部组件</title>
</head>
<body>
<div id="app">
<parent>

</parent>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let grandson = {template: '<div>grandson</div>'};
let son = {
template: '<div>son <grandson></grandson></div>',
components: {
grandson
}
};
let parent = {
template: '<div>parent <son></son> </div>',
components: {
son
}
};
let vm = new Vue({
el: '#app',
components: {
parent,
}
})
</script>
</body>
</html>

属性传递(父给子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>属性传递(父给子)</title>
</head>
<body>
<div id="app">
父亲:{{money}}
<!--当前组件属性=父级的值-->
<!--相当于给child加了一个m属性,属性对应的数据是父亲的money-->
<child :m="money"></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
money:100,
},
components: {
child:{
props:['m'], // 相当于 this.m = 100,会在当前子组件上,生成m属性。值是父亲的
template:'<div>儿子{{m}}</div>',
computed:{
money(){
return m;
}
}
}
}
})
</script>
</body>
</html>

子组件中,设置一个props属性,值是一个数组,数组里面每个值通过v-bind绑定一个父组件的数据。这样在子组件中就可以拿到这个值
在props中还可以写对象,可以对传递的值做一些校验、设置默认值、设置是否必须等操作:

1
2
3
4
5
6
7
props:{ // 如果传递值的时候 :m="money" money此时是js变量。如果是m='money'则一定是字符串。
m:{
type:[String,Boolean,Function,Object,Array,Number], // 校验属性类型
default:0, // 如果m的值没有传递 默认为0
required:true, // 此属性是否一定要传递, required和default不可以同用
}
}

$emit(子给父)

子组件向父组件传值:自定义事件,this.$emit,发送信息,在父组件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate">
<title>属性传递(父给子)</title>
</head>
<body>
<div id="app">
<!--父亲绑定一些事情,儿子触发这个事情 将参数传递过去,单向数据流父亲数据刷新,儿子就刷新-->
父亲:{{money}}

<!--child.$on('child-msg',things)-->
<child :m="money" @child-msg="things"></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data:{money:100},
methods:{
things(val){
this.money = val;
}
},
components:{
child:{
props:['m'],
template:'<div>儿子:{{m}} <button @click="getMoney()">加钱</button> </div>',
// 触发自己的自定义事件 让父亲的方法执行 是基于Vue的发布订阅模式
methods:{
getMoney(){
this.$emit('child-msg',800);
}
}
},
}
})
</script>
</body>
</html>

可以看到操作子类组件,可以传递给父组件。

slot插槽

slot中可以放置一些默认的内容,如果传递了内容则替换掉
每一个slot都需要添加一个name属性,用于插拔时候匹配。默认的内容,name=’dafault’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--这里放置的内容均属于父级当前模板的,只有属性名是属于组件的-->
<a-modal>
<a href="http://www.baidu.com">去百度</a> <!--如果没有指定slot,默认都会防止到default中-->
<h1 slot="title" @click="fn">是否删除</h1> <!--里面的事件绑定定义的方法,需要定义在父级中-->
<p slot="content">确认删除吗</p>
<a href="http://www.baidu.com">去百度</a>
</a-modal>

</div>

<!--模板中只能有一个根元素.可以通过元素属性定制模板-->
<!--slot中可以放置一些默认的内容,如果传递了内容则替换掉-->
<template id="amodel">
<div>
<slot name="title">默认标题</slot>
<slot name="content">默认内容</slot>
<slot name="default">这是一个默认标题</slot>
</div>
</template>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let aModal = {
template: '#amodel',
};
let vm = new Vue({
el: '#app',
components: {
aModal
},
data: {},
methods:{
fn(){
alert('father');
}
}
})
</script>
</html>

可以看到,标签都插入了solt的值对应的name的值的位置。

ref关键字

ref关键字,可用于父操作子(包括调用方法,操作属性,操作dom等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ref关键字,可用于父操作子(包括调用方法,操作属性,操作dom等)</title>
</head>
<body>
<!--子传父:发布订阅(emit\on) 父传子:属性传递 -->
<div id="app">
<loading ref="load"></loading>
</div>

</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// 父组件调用子组件的方法
let loading = {
template: '<div v-if="flag">疯狂加载中。。。。。。</div>',
data(){
return {
flag:true,
}
},
methods:{
hide(){
this.flag = false;
}
}
};
let vm = new Vue({
el: '#app',
components: {
loading
},
mounted(){ // refs如果放在组件上,获取的是组件的实例,并不是组件dom元素
this.$refs.load.$el.style.background = 'red';
},
data: {},
})
</script>
</html>

使用vue开发时经常会用到ref属性,ref属性有什么作用呢?我刚开始用的时候发现这个属性有点鸡肋,总是感觉可有可无,但是随着不断的使用vue,慢慢地发现它的作用还是挺重要的,比如:有时候我们想在父组件中调用子组件的方法或属性,这个时候该怎么做呢?可以通过为子组件设置ref,然后通过this.$refs.refName(refName为子组件的ref值)获取到子组件,然后就可以随意调用子组件的方法和属性了。又比如:有时候我们想操作子组件或HTML标签的DOM,在vue中我们几乎不使用class或id来获取元素的DOM,这个时候该怎么做呢?可以为子组件或想要操作的DOM标签添加ref属性,然后通过this.$refs.refName.$el或者this.$refs.refName来获取DOM。
那么在使用ref时要注意什么呢?

根据ref使用的对象不同,获取到的结果也是不一样的:

  1. 自定义组件使用ref属性,通过ref值可获取到该自定义组件;
  2. 普通HTML标签使用ref属性,通过ref值获取到的是该标签对应的DOM;


keep-alive标签

一般用作缓存:为了后面的路由做准备,如果缓存了就不会再走created() mounted()钩子函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate">
<title>冒泡和抓取</title>
</head>
<body>
<div id="app">
<input type="radio" v-model="radio" value="home">home
<input type="radio" v-model="radio" value="list">list
<keep-alive>
<component :is="radio"></component>
</keep-alive>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let home = {template: '<div>home</div>'};
let list = {template: '<div>list</div>'};
let vm = new Vue({
el: "#app",
components: {
home, list
},
data: {
radio: 'home',
}
})
</script>
</html>

可以看到dom被缓存了下来


同级组件传值eventBus

组件与组件之间传值,可以通过eventBus来发布和订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<brother1></brother1>
<brother2></brother2>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let EventBus = new Vue;
let brother1 = {
template:'<div>{{color}} <button @click="change">变绿</button></div>',
data(){
return {color:'绿色',old:'绿色'}
},
created(){
EventBus.$on('changeRed',(val)=>{
this.color = val;
});
},
methods:{
change(){
EventBus.$emit('changeGreen',this.old);
this.color = this.old;
}
}
};
let brother2 = {
template:'<div>{{color}} <button @click="change">红色</button></div>',
data(){
return {color:'红色',old:'红色'};
},
methods:{
change(){
EventBus.$emit('changeRed',this.old);
this.color = this.old;
}
},
created(){
EventBus.$on('changeGreen',(val)=>{
this.color = val;
})
}
};
let vm = new Vue({
el:'#app',
components:{
brother1,brother2,
}
})
</script>
</html>

可以看到结果:


路由router

如今开发大多都是前后的分离,后端只负责提供前端接口。跳转都是前端自己处理的。
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

hash 和 history

对于 Vue 这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。

为了达到这一目的,浏览器当前提供了以下两种支持:

  1. hash —— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。
    比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
  2. history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)
    这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

因此可以说,hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。

一般场景下,hash 和 history 都可以,除非你更在意颜值,# 符号夹杂在 URL 里看起来确实有些不太美丽。

调用 history.pushState() 相比于直接修改 hash,存在以下优势:

  1. pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
  2. pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  3. pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
  4. pushState() 可额外设置 title 属性供后续使用。

当然啦,history 也不是样样都好。SPA 虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。

  1. hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
  2. history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

声明式路由导航

即<router-link>

例如: To PageB </router-link>

注意: 会默认解析成 a 标签,可以通过 tag 属性指定它解析成什么标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>路由</title>
<style>
.router-link-active{ // 当router-link被激活后
color:red;
}
.active{
color: pink;
}
</style>
</head>
<body>
<!--前后端分离,后端只负责提供前端接口。跳转都是前端自己处理的-->
<!--hash模式,开发时用hash模式,不会导致404问题
h5的history.pushState模式,如果用hash的方式,不支持SEO。上线采用h5的跳转-->
<div id="app">
<!--router-view是一个全局组件,可以直接使用-->
<!-- <a href="#/home">home</a> history模式下失效,只有hash模式下才有用
<a href="#/list">list</a>-->
<router-link to="/home" tag="button">首页</router-link> <!--这个跳转方式会自动匹配hash模式和h5模式-->
<router-link to="/list" tag="span">内容页</router-link>
<router-view></router-view>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<!--vue-router要放在vue后-->
<script src="../node_modules/vue-router/dist/vue-router.js"></script>
<script>
let home = {template: '<div>主页</div>',};
let list = {template: '<div>内容页</div>',};
let routes = [ // 路由的映射表 配置路径和组件的关系
{path:'/home',component:home}, // 配置的关系 是 一个页面一个组件
{path:'/list',component:list}, // 配置的关系 是 一个页面一个组件
];
let router = new VueRouter({
mode:'history', // 默认是hash模式 这里手动设置成history模式 h5模式
routes:routes, // 或者直接写 routes
linkActiveClass:'active', // 设置激活样式 默认为 router-link-active

});
let vm = new Vue({
el:'#app',
components:{
home,list
},
router:router, //或者直接写 router
})
</script>
</html>

结果:

编程式路由导航

即写js的方式
相关 API:

  1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面) ==>> 队列的方式(先进先出)
  2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面) ==>> 栈的方式(先进后出)
  3. this.$router.back(): 请求(返回)上一个记录路由
  4. this.$router.go(-1): 请求(返回)上一个记录路由
  5. this.$router.go(1): 请求下一个记录路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>路由</title>
<style>
.router-link-active {
/ / 当router-link被激活后 color: red;
}

.active {
color: pink;
}
</style>
</head>
<body>
<div id="app">
<!--编程式导航加声明式导航,在js跳转页面-->
<router-link :to="{path:'/home'}">首页</router-link>
<router-link :to="{path:'/list'}">列表页</router-link>

<router-view></router-view>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<!--vue-router要放在vue后-->
<script src="../node_modules/vue-router/dist/vue-router.js"></script>
<script>
let home = {
template: '<div>首页 <button @click="toList">去列表</button></div>',
methods:{
toList(){
this.$router.push('/list') // 去某一个路径
// 也可以用 this.$router.replace('/list') 无痕处理
// 理解 :
// 1.['/']
// 2.['/','/home']
// 3.replace:['/','/list'] 直接覆盖home 擦除了痕迹
// 3.push:['/','/home','list'] 没有擦层痕迹
// 3.go(-1):['/'] 返回1层

}
}
};
let list = {
template: '<div>列表页 <button @click="back">去首页</button></div>',
methods:{
back(){
this.$router.go(-1) // 返回上1级 -1
}
}
};
let error = {
template: '<div>错误页面 <button @click="back">去首页</button></div>',
methods:{
back(){
this.$router.push('/home') // 返回上1级 -1
}
}
};
let routes = [
{path: '', component: home}, // 默认展示的路由
{path: '/home', component: home},
{path: '/list', component: list},
// {path: '/*', component: error},
// 这个路径不会变 只是切换了组件而已
// 都匹配不到的时候 匹配最后一个
{path: '/*', redirect:'/home'}, // 这样写 路径就会跳转

];
let router = new VueRouter({
routes,
});
let vm = new Vue({
el: '#app',
router, // 每个组件 都会拥有一个名字叫$router的属性,和一个叫$route属性
// 有r(router)的放的都是方法,没r(route)的存的都是属性
})
</script>
</html>

多重目录

当需要实现多重目录(子目录还有子目录)的时候,我们可以将<router-link>标签嵌入到template中。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>路由</title>
<style>
.router-link-active {
// 当router-link被激活后 color: red;
}
</style>
</head>
<body>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/detail">详情</router-link>
<router-view></router-view>
</div>
<template id="detail">
<div>
<router-link to="/detail/profile">个人中心</router-link>
<router-link to="/detail/about">关于我</router-link>
<router-view></router-view>
</div>
</template>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<!--vue-router要放在vue后-->
<script src="../node_modules/vue-router/dist/vue-router.js"></script>
<script>
let home = {
template: '<div> home </div>',
};
let detail = {template: '#detail',};
let proFile = {template: '<div> proFile </div>',};
let aboutMe = {template: '<div> aboutMe </div>',};
let routes = [
{path: '/home', component: home},
{
path: '/detail', component: detail,
children:[ // children中的路径 永远不能以/开头,以/开头表示一级路由
{path:'profile',component:proFile},
{path:'about',component:aboutMe},
]
},
];
let router = new VueRouter({
routes,
});
let vm = new Vue({
el:'#app',
router,
})
</script>
</html>

有一定基础的话,上面的代码一定很轻松的看的懂,在这里就不做过多的注释。结果:

参数传递

利用url传递参数(以 :冒号形式的形式传递参数)

1.以:冒号的形式配置参数:(这里需要传递的参数是 a):

1
2
3
let routes = [
{path: '/article/:a', component: article},
];

2.在页面里,输出新闻number:

1
<div>这是路径{{$route.params.a}}</div>

3.写入标签,方便直接利用url传值:

1
2
3
4
5
6
<div id="app">
<router-link to="/article/1">到路径1</router-link>
<router-link to="/article/2">到路径2</router-link>
<router-link to="/article/3">到路径3</router-link>
<router-view></router-view>
</div>

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate">
<title>路由</title>
<style>
.router-link-active {
/ / 当router-link被激活后 color: red;
}
</style>
</head>
<body>
<div id="app">
<router-link to="/article/1">到路径1</router-link>
<router-link to="/article/2">到路径2</router-link>
<router-link to="/article/3">到路径3</router-link>
<router-view></router-view>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<!--vue-router要放在vue后-->
<script src="../node_modules/vue-router/dist/vue-router.js"></script>
<script>
let article = {
template:'<div>这是路径{{$route.params.a}}</div>',
};
let routes = [
{path: '/article/:a', component: article},
];
let router = new VueRouter({
routes,
});
let vm = new Vue({
el:'#app',
router,
})
</script>
</html>

结果:

-------------本文结束感谢您的阅读-------------