Kyle's Notebook

Vue 学习笔记

Word count: 9.7kReading time: 48 min
2019/05/20

Vue 学习笔记

学习测试环境可通过 CDN 地址引入 Vue(项目开发和线上环境一般使用 vue-cli、webpack 打包)。

1
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

可使用构造函数创建 Vue 实例,即应用的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
{{ msg }}
</div>
<script>
let app = new Vue({
el: '#app', // 指定页面中存在的 DOM 元素挂载
data: { // 声明需要双向绑定的数据(渲染到页面上)
msg: 'Hello World!'
}
})
console.log(app.$el) // 访问 Vue 实例中的属性
console.log(app.msg) // 访问 Vue 实例 data 域中的属性
</script>

与 jQuery 对比

使用 jQuery 实现 todo-list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
<ul id="ul-list"></ul>
</div>
<script type="text/javascript">
var $txtTitle = $('#txt-title')
$('#btn-submit').click(function () {
var title = $txtTitle.val()
if (!title)
return

var $li = $('<li>' + title + '</li>')
$('#ul-list').append($li)
$txtTitle.val('')
})
</script>

使用 Vue 实现 todo-list:

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
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</div>
<script type="text/javascript">
var data = {
title: '',
list: []
}
new Vue({
el: '#app',
data: data,
methods: {
add: function () {
this.list.push(this.title)
this.title = ''
}
}
})
</script>
  • 区别在于:jQuery 以选择器获取并修改 DOM,Vue 以数据与 DOM 元素绑定、DOM 响应数据变化;
  • 即数据和视图分离(开放封闭原则)、以数据驱动视图(DOM 操作被封装)。

生命周期

一些生命周期钩子:

  1. created:实例创建完成后调用,完成了数据的观测等而尚未挂载($el还不可用)需要初始化处理一些数据时会用到;
  2. mounted:el挂载到实例上后调用,一般第一个业务逻辑会在这里开始。相当于$(document).ready()
  3. beforeDestroy:实例销毁之前调用。主要解绑一些使用 addEventListener 监听的事件等;

文本插值,表达式

使用双大括号( Mustache 语法)是最基本的文本插值方法,可以将双向绑定的数据实时显示,还可以使用单行 JS 表达式:

1
2
3
4
{{ 1 + 1 }}
{{ 6 > 5? msg1: msg2 }}
{{ var a = 6 }} // 错误,这是多行表达式
{{ if(6 > 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
<div id="dateApp">
{{ date }}
</div>
<script>
let app = new Vue({
el: '#dateApp',
data: {
date: new Date()
},
mounted: function() {
let _this = this; // this 表示 Vue 实例
this.timer = setInterval(
() => {
_this.date = new Date(); // 每秒刷新1次
}, 1000
)
},
beforeDestroy: function() {
if (this.timer) {
clearInterval(this.timer) // 如定时器存在即销毁
}
}
})
</script>

过滤器

尾部添加一或多个管道符 | 可对数据进行过滤,常用于格式化文本(字母全部大写、货币千位使用逗号分隔等):

以刚才的计时器为例

1
{{ date | formatDate }}

在 Vue 实例中定义过滤器函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let plusDate = function(value) {
return value < 10? '0' + value: value
}

let app = new Vue({
el: '#dateApp',
data: {
date: new Date()
},
filters: {
formatDate: function(value) {
let date = new Date(value); // 将字符串转换为date类型
let year = date.getFullYear();
let month = plusDate(date.getMonth() + 1);
let day = plusDate(date.getDate());
return `{year}-{month}-{day}`
}
}

过滤器中也可以传参数,如 date | formatDate(66,99)formatDate 的第一个和第二个参数,分别对应过滤器函数的第二个和
第三个参数(第一个参数为 date

计算属性

  1. 在一个计算属性里可以完成复杂的逻辑(运算、函数调用),最终返回一个结果;
  2. 计算属性还可以依赖多个 Vue 实例的数据,只要其中任一数据变化,计算属性就会重新执行并更新视图;
  3. 可以为计算属性(默认为 getter )定义 getter 和 setter 方法对属性进行读写;
  4. 缓存: 不管渲染不渲染,只要计算属性依赖的数据未发生变化,就永远不变(使用计算属性还是 methods 取决于是否需要缓存,当遍历大数组和做大量计算时应当使用计算属性做缓存)。

实例:计算购物车总价

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
<div id='app'>
{{ price }}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
package1: [
{name: 'x', price: 6999, count: 2},
{name: 'y', price: 7999, count: 3}
],
package2: [
{name: 'x', price: 2999, count: 5},
{name: 'y', price: 3999, count: 6}
]
},
computed: {
price: function() {
let price = 0
for (let i = 0; i < this.package1.length; i++) {
price += this.package1[i].price * this.package1[i].count;
}
for (let j = 0; j < this.package2.length; j++) {
price += this.package2[j].price * this.package2[j].count;
}
return price
}
// 计算属性直接跟一个 function,默认是 getter 方法,等价于
/**
price: {
get: function() {
...
}
}
*/
// 也可以定义计算属性的 setter 方法,在外部可以调用:app.price = 'xxx'
/**
price: {
set: function() {
...
}
}
*/
}
})
</script>

监听属性变化

除了使用计算属性 computed 以外,还可以使用 watch 监听单个属性的变化,两者对比:

computed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="app">
firstName: <input type="text" v-model="firstName">
lastName: <input type="text" v-model="lastName">
<br>
{{ fullName }}
</div>

<script>
let app = new Vue({
el: "#app",
data: {
firstName: "",
lastName: ""
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
})
</script>

watch:

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
<div id="app">
firstName: <input type="text" v-model="firstName">
lastName: <input type="text" v-model="lastName">
<br>
{{ fullName }}
</div>

<script>
let app = new Vue({
el: "#app",
data: {
firstName: "",
lastName: "",
fullName: ""
},
watch: {
firstName(val) {
this.fullName = `${val} ${this.lastName}`
},
lastName(val) {
this.fullName = `${this.firstName} ${val}`
}
}
})
</script>

基本指令

指令是 Vue 模板中最常用的一项功能,在 HTML 元素中带有前缀 v- 表示,有助于快速完成 DOM 操作,最常用的几个指令:

  1. v-­text:解析文本,和双花括号作用一样
  2. v­-html:解析html元素
  3. v­-bind:动态更新元素上的属性,如 id、class 等,当数据变化时就会重新渲染。
  4. v-­on:绑定事件监听器
    1
    2
    <div v-bind:class='className'></div>
    <button v-on:dbclick='count'></button>

语法糖:指在不影响功能的情况下,添加某种简洁方法实现同样的效果,从而更加方便程序开发:

  1. v-bind =》 :
  2. v-on =》 @

v-bind

绑定值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id='app'>
<a v-bind:href="url">baidu</a> <!-- 使用v-bind绑定活的属性 -->
<img :src="imgUrl" alt="">
</div>

<script>
new Vue({
el: "#app",
data: {
url: "http://www.baidu.com"
imgUrl: "https://www.baidu.com/img/bd_logo1.png?where=super"
}
})
</script>

绑定Class(绑定Style同理):

  1. v­bind:class 设置一个对象/数组可以动态地切换 class ,条件复杂时可以使用计算属性
  2. Vue 中驼峰式命名的大写字母都会被转换成中划线分隔的小写字母(建议统一写驼峰);
    1
    2
    <div :class="{divStyle: isActive, borderStyle: isBorder}"></div>   <!-- isActive的值为 true 为激活,false 为不激活 -->
    <div :class="[activeClass,errorClass]"></div> <!-- 数组中的元素(变量)即为 class 类名 -->

v-cloak

解决初始化慢导致页面闪动的问题,一般与 display: none 结合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
[v-cloak]: {
display: none
}
</style>

v-cloak: <p v-cloak:>{ { msg } }</p> <!-- Vue 实例结束编译时移除,才会渲染msg -->

<script>
while (true) {

}

new Vue({
el: "#app",
data: {
msg: "Hello World!"
}
})

</script>

v-once

只渲染一次,后续修改不会重新渲染

条件渲染指令:v-if,v-else-if,v-else

  1. v-ifv-else-if 后接等号和必须返回布尔值的条件,满足条件(结果为 True)才渲染,否则会从 DOM 中移除;
  2. v-if 在渲染元素时出于效率考虑,会尽可能复用已有元素而非重新渲染,所以可能出错(只渲染变化的元素,实例:用户名与密码的 input 框);
  3. 上述问题的解决方法:为元素加上 key=keyName(使元素有差异而重新渲染)。

条件渲染指令:v-show

  1. v-if 类似,显现与否取决于布尔值;
  2. v-if 的区别在于 v-show 值为 false 时相当于 display: none,页面上仍然存在该元素。

列表渲染指令:v-for

数组遍历或枚举对象属性时可使用 v-for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul>
<!-- arr: [{name: 'ywh'}, 'name': 'hwy'] -->
<li v-for="item in arr">
{{ item.name }}
</li>

<li v-for="(item, index) in arr">
{{ index }}-{{ i.name }}
</li>

<!-- obj: {name: 'ywh', 'age': 18, gender: 'male'} -->
<li v-for="(value, key, index) in obj">
{{ value }}-{{ key }}-{{ index }}
<li>
<ul>

方法与事件

当方法有参数,但 @click 指定方法时没有加括号 @click="handle" 会默认传原生事件对象(object MouseEvent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
{{ count }}
<button @click="handle()">加1</button> <!-- 建议当方法有参数时即使不传参也加上括号 -->
<button @click="handle(10)">加10</button>
</div>

<script>
let app = new Vue({
el: "#app",
data: {count: 0}
methods: {
hadnle: function(count) {
count = count || 1;
this.count += count
}
}
})

</script>

数组更新

涉及数组的操作中,可使用js更改数组的方法:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse

但其中两种情况下数组变动 Vue 会检测不到(不会重新渲染):

  1. 改变数组指定项:this.arr[0] = "car"
  2. 改变数组长度:this.arr.length = 1

解决方法:

1
2
Vue.set(app.arr, 0, "car")      // 改变数组指定项(arr[0])
app.arr.splice(0) // 改变数组长度(删除数组所有元素)

数组过滤:

1
2
3
4
5
6
7
8
9
10
datra: {
arr: ["book", "pen", "pencil"]
},
computed: {
matchOO: function() {
return this.arr.filter(function (value){
return value.match(/oo/)
})
}
}

修饰符

在 Vue 中传入Event对象用 $event

  1. stop:定义在内层元素,阻止事件向上冒泡(<button @click.stop='btnClick()'>
  2. prevent:提交事件并阻止页面重载(<form action="" @submit.prevent="handle">...,handle 为触发事件执行函数)
  3. self:定义在外层元素,只作用在元素本身而非子元素时调用(与 stop 相反,如 button 的外层 div)
  4. once:只执行一次的方法(<button @click.once='btnClick()'>

键盘事件

1
<input @keyup.13 = "submit">    <!-- 13 即 enter 键,具体查看键盘编码号 -->

其中Vue提供了键盘事件的简写:

  1. .enter
  2. .tab
  3. .delete

表单与 v-model

输入框和编辑框

  1. 使用 v­-model 指令用于在表单类元素(可以用于 input 框,以及 textarea 等)上双向绑定事件;
  2. 框内所显示的值只依赖于所绑定的数据,不再关心初始化时插入的 value。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <div id="app">
    <button v-on:click="reverse()">reverse</button>
    <input v-model="content">
    </div>
    <script>
    let app = new Vue({
    el: "#app",
    data: {
    content: "hello World",
    },
    methods: {
    reverse(){
    this.content = this.content.split("").reverse().join("")
    }
    }
    })
    </script>

单选按钮

  1. 单个单选按钮,直接用 v-­bind 绑定一个布尔值,用 v-­model 是不可以的;
  2. 多个单选按钮组合使用时需要 v­-model 来配合 value 使用,绑定选中的单选框的 value 值,此处所绑定的初始值可以随意。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="app">
    <input type="radio" value="apple" v-model="checks">apple <!-- 多个复选框 -->
    <input type="radio" value="banana" v-model="checks">banana
    <input type="radio" value="orange" v-model="checks">orange
    选中:{{ checks }}
    </div>
    <script>
    let app = new Vue({
    el: "#app",
    data: {
    checks: ""
    }
    })
    </script>

复选框

  1. 单个复选框,直接用定一个布尔值,可以用 v-­modelv-­bind
  2. 多个复选框组合使用时需要 v-­model 来配合 value 使用,v­-model 绑定一个数组;如果绑定的是字符串则会转化为布尔值,与所有绑定的复选框的 checked 属性相对应。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="app">
    <input type="checkbox" value="apple" v-model="checks">apple <!-- 多个复选框 -->
    <input type="checkbox" value="banana" v-model="checks">banana
    <input type="checkbox" value="orange" v-model="checks">orange
    选中:{{ checks }}
    </div>
    <script>
    let app = new Vue({
    el: "#app",
    data: {
    checks: []
    }
    })
    </script>

下拉框

  1. 单选下拉框,所绑定的 value 值初始化可以为数组或字符串,有 value 直接优先匹配一个 value 值,没有则匹配一个 text 值;
  2. 多选下拉框需要 v-­model 来配合value使用,v-­model 绑定一个数组,与复选框类似。
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
<input type="text" v-model="value"> {{ value }}        

<textarea v-model="msg">xxx</textarea> {{ msg }}

<input type="radio" v-bind:checked="singleRadio"> <!-- 单个单选框:绑定布尔值 -->

<input type="radio" name="checks" v-model="checkName" value="apple"> apple
<input type="radio" name="checks" v-model="checkName" value="orange"> orange <!-- 多个单选框,使用name关联,通过v-model绑定选中的按钮的value值 -->
<input type="radio" name="checks" v-model="checkName" value="banana"> banana
选中:{{ checkName }}

<input type="checkbox" value="apple" v-model="checks">apple <!-- 多个复选框 -->
<input type="checkbox" value="banana" v-model="checks">banana
<input type="checkbox" value="orange" v-model="checks">orange
选中:{{ checks }}

<select v-model="seleted">
<option value="apple">apple</option>
<option value="banaba">banana</option>
<option value="orange">orange</option>
</select>
选中:{{ seleted }}

<select v-model="seleted" multiple>
<option value="apple">apple</option>
<option value="banaba">banana</option>
<option value="orange">orange</option>
</select>
选中:{{ selectedmul }}

<script>
let app = new Vue({
el: "#app",
data: {
value: "Hello World!",
msg: "Hello",
singleRadio: true,
checkName: "apple",
checks: "xyxyxyx",
seleted: [],
selectedmul: []
}
})
</script>

总结:

  • 对于单选,初始化最好给定字符串,v-­model 此时绑定的是静态字符串或者布尔值;

  • 对于多选,初始化最好给定一个数组。

绑定值

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
<input type="radio" v-model="picked" v-bind:value="value"> {{ picked }}

<input type="checkbox" v-model="toggle", :true-value="value1" :false-value="value2"> <!-- 选中和不选中对应不同的值 -->

{{ toggle == value1 }}
{{ toggle == value2 }}

<select v-model="valueSelect" :value="{num: 1}">
<option value="apple">apple</option>
<option value="banaba">banana</option>
<option value="orange">orange</option>
</select>

<script>
let app = new Vue({
el: "#app",
data: {
picked: true,
value: "Hello World!",
toggle: true,
value1: "选中",
value2: "不选中",
valueSelect: {}
}
})
</script>

组件

使用组件可提高代码的复用性。

命名规则

Vue 组件中 camelCased(驼峰式)命名与 kebab­case(短横线命名):

  • 在html中,myMessagemymessage 是一致的,因此在组件中的 HTML 中使用必须使用 kebab­case(短横线)命名,不允许使用驼峰!

  • 在组件中, 父组件给子组件传递数据必须用短横线。在 template 中,必须使用驼峰命名方式,若为短横线命名则会报错;

  • 在组件的 data 中,用 this.XXX 引用时只能是驼峰命名方式,若为短横线命名则会报错。

组件注册

全局注册(所有 Vue 对象都可以使用,但权限太大,容错率低)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<my-component></my-component>
</div>

<script>
Vue.component(
'my-component', {
template:'<div>我是组件的内容</div>' // 组件中的内容会被模板替换
}
)

var app = new Vue({
el: "#app",
data: {

}
})
</script>

局部注册(Vue 对象中可用)

1
2
3
4
5
6
7
8
var app = new Vue({
el: '#app',
components:{
'my-component':{
template: '<div>我是组件的内容</div>'
}
}
})

Vue 组件模板在某些情况下会受到 HTML 标签的限制:此时可以使用 is 属性来挂载组件:

1
2
3
4
5
<!-- 比如<table>中只能有<tbody>,<tr>,<td>等元素,所以直接在 `table` 中使用组件是无效的, -->

<table>
<tbody is="my-component"></tbody>
</table>
  • 必须使用小写字母加“­”命名组件(child、my­componnet);

  • template 中的内容必须被一个 DOM 元素包括,也可以嵌套;

  • 在组件的定义中,可以使用除了 template 之外的其他选项:datacomputedmethods

data 必须是一个方法:

1
2
3
4
5
6
7
8
9
10
// ...
'btn-component': {
template: '<button @click="count++">'{{ count }}</button>,
data: function () {
return { // 专属于组件的对象
count: 0
}
}

}

数组验证

Vue 中验证的数据类型有:

  • String
  • Number
  • Boolean
  • Object
  • Array
  • Function
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
<div id="app">
<type-component :a="a" :b="b" :d="d" :f="f" :g="g"></type-component>
</div>
<script>
// 数据验证组件
Vue.component('typeComponent', {
props: {
a: Number,
b: [String, Number], // 传入的b只允许是String或Number
c: {
type: Boolean,
default: true
},
d: {
type: Number,
required: true

},
e: {
type: Array,
default: function () {
return [];
}
},
f: {
validator: function (value) {
return value > 10;
}
},
g: {
type: Function
}
},
template: '<div>{{ a }} - {{ b }} - {{ d }} - {{ f }}</div>',
})

let app = new Vue({
el: "#app",
data: {
a: 1, b: 567, d: 789, f: 99, g: 1111
}

})
</script>

组件通信

父组件向子组件传递数据

  • 在组件中使用 props 来从父亲组件接收参数,在 props 中定义的属性,都可以在组件中直接使用;

  • 组件中 propps 来自父级,而 data return 的数据就是组件自己的数据,两种情况作用域就是组件本身,可以在 templatecomputedmethods 中直接使用;

  • props 可以设置字符串数组或对象,以使用 v­-bind 动态绑定父组件来的内容。

父组件data: {parentMsg: "Hello World!"}
-> 子组件<bind-component v-bind:msg="parentMsg"></bind-component>
-> 子组件"bind-component": {props: ["msg"]}
-> 子组件"child-component": {template: "<div>{{ msg }}</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
<div id="father" style="border: 2px solid chartreuse; height: 160px">
<h5 style="text-align: center">父组件</h5>
<child-component msg="来自父组件的内容"></child-component> <!-- 父组件向子组件传递数据 -->
<input type="text" v-model="parentMsg">
<bind-component v-bind:msg="parentMsg"></bind-component> <!-- 父组件的parentMsg绑定在子组件的msg中 -->
</div>

<script>
let app = new Vue({
el: "#father",
data: {
parentMsg: "Hello World!"
},
components: {
"child-component": {
template: "<div>{{ msg }}</div>",
props: ["msg"],
},
"bind-component": {
template: "<div>{{ msg }}</div>",
props: ["msg"],
}
}
})
</script>

单向数据流:

  1. 通过 props 传递数据是单向的,父组件数据变化时会传递给子组件,但不能反过来;
  2. 单向传递的目的是尽可能将父子组件解稿,避免子组件无意中修改了父组件的状态。

当父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="app">
<my-component msg="父组件传递到子组件的数据"></my-component>
</div>

<script>
// 注册组件
Vue.component('my-component', {
props: ['msg'], // 将父组件的数据传递进来,并在子组件中用props接收
template: '<div>子组件</div>'
data: function () {
return {
count: this.msg // 将传递进来的数据通过初始值保存起来
// props中的值可以通过this.XXX直接获取
}
}
})
let app = new Vue({
el: "#father",
data: {}
})
</script>

prop 作为需要被转变的原始值传入:

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
<div id="app">

<input type="text" v-model="width">
<my-comp :width="width"></my-comp>
</div>

<script>
var app = new Vue({
el: '#app',
data: {
width: '100'
},
components: {
'my-comp': {
props: ['width'],
template: '<div :style="style">{{ width }}px</div>',
computed: {
style: function () {
return {
width: this.width + 'px',
background: 'red'
}
}
}
}
}
})
</script>

子组件向父组件传递数据

子组件 <button @click='increase'>+1</button>
-> 子组件 this.$emit('change', this.count)
-> 父组件 <child @change="handldTotal"></child>
-> 父组件 handldTotal: function(value) {this.total = 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
26
27
28
29
30
31
32
33
34
<div id="app">
{{ total }}
<child @change="handldTotal"></child> <!-- 也可以在组件中使用 `v-model` 的方法实现 -->
<!--<child v-model="total"></child>-->
</div>

<script>
Vue.component('child', {
template: "<div><button @click='increase'>+1</button></div>",
methods: {
increase: function () {
this.count += 1
this.$emit('change', this.count) // 子组件向父组件发出发生change事件的通知
// this.$emit('input', this.count) // 使用v-model时指定为input事件通知
}
},
data: function () {
return {
count: 200
}
}
})
let app = new Vue({
el: "#app",
data: {
total: 200
},
methods: {
handldTotal: function(value) {
this.total = value
}
}
})
</script>
  1. $emit 实际上会触发一个 input 事件, 其后的参数就是传递给 v­-model 绑定的属性的值
  2. v­-model 其实是一个语法糖,这背后做了两个操作:
    i. v-­bind 绑定一个 value 属性
    ii. v-­on 指令给当前元素绑定 input 事件

非父组件之间的通信

可以使用一个空的 Vue 实例作为中央事件总线:

1
2
3
4
5
6
7
8
9
+-------------+
| 根组件 |
| +---------+ |
| | 子组件 A | |
| +---------+ |
| +---------+ |
| | 子组件 B | |
| +---------+ |
+-------------+
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
<div id="app">
<a-component></a-component>
<b-component></b-component>
<br>
<child-component></child-component>
{{ msg }}
</div>

<script>
Vue.component('a-component', {
template: '<div style="width: 100px; height: 100px; border: 1px solid black"><button @click="handle">点击向B组件传递数据</button></div>',
data: function () {
return {
a: '来自A组件的内容'
}
},
methods: {
handle: function () {
this.$root.bus.$emit('send', this.a)
}
}
})
Vue.component('b-component', {
template:'<div style="width: 100px; height: 100px; border: 1px solid black">{{ b }}</div>',
data: function() {
return {
b: ""
}
},
created: function () {
let self = this
this.$root.bus.$on('send', function (value) {
self.b = value
})
}
})

Vue.component('child-component', {
template: '<button @click="setFatherData">通过点击修改父亲的数据</button>',
methods: {
setFatherData: function () {
this.$parent.msg = '数据已修改'
}
}
})

let app = new Vue({
el: "#app",
data: {
bus: new Vue(),
msg: '数据未修改',
formchild: '未取得数据'
},
methods: {
getChildData: function() {
this.formchild = this.$refs.c.msg;
}
}
})
</script>

使用 Slot 分发内容

让组件可以组合的过程被称为内容分发,使用特殊的 slot 元素作为原始内容的插槽。

编译的作用域

1
2
3
<child-component>
{{ message }}
</child-component>

其中 message 应该绑定到父组件的数据,组件作用域简单地说是:

  • 父组件模板的内容在父组件作用域内编译;

  • 子组件模板的内容在子组件作用域内编译。

插槽的用法

将父组件的内容与子组件相混合,从而弥补了视图的不足:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<my-component>
<p>我是父组件的内容</p>
<!-- 最外层组件内部的所有内容都由最外层组件控制 -->
<!-- 如果没有使用slot,则子组件无法在其内部插入内容 -->
</my-component>
</div>
<script>
Vue.component('my-component', {
template:
`<div>
<slot>如果父组件没有插入内容,我就作为默认出现</slot>
</div>`
})
new Vue({
el: "#app"

})
</script>

其中 <slot> 标签中可以指定 name,称为“具名插槽”,对应 DOM 标签的 slot 属性:

1
2
3
4
5
6
7
<p slot="footer">底部</p>

// ...
template: `
<div class="footer">
<slot name="footer"></slot>
</div>`

作用域插槽

使用一个可以复用的模板来替换已经渲染的元素。

从子组件获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<my-component>
<template slot="abc" slot-scope="prop"> <!-- template 是不会被渲染的,2.5.0版本后不需要写 template -->
{{ prop.text }} <!-- text 在 slot 中定义 -->
</template>
</my-component>
</div>
<script>
Vue.component('my-component', {
template: `
<div>
<slot text="来自子组件的数据" name="abc"></slot>
</div>
`
})
new Vue({
el: "#app"
})
</script>

访问插槽

通过 this.$slots.name 可以访问名称为 name 的插槽。

1
2
3
4
5
6
7
8
9
mounted:function () {
// 访问插槽
var header = this.$slots.header;
var text = header[0].elm.innerText;
var html = header[0].elm.innerHTML;
console.log(header)
console.log(text)
console.log(html)
}

动态组件

Vue 提供了 component 元素用来动态的挂载不同的组件,使用 is 特性实现。

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
<div id="app">
<!-- 点击不同按钮实现切换不同组件 -->
<component :is="thisView"></component>
<button @click="handleView('A')">1</button>
<button @click="handleView('B')">2</button>
<button @click="handleView('C')">3</button>
</div>
<script>
Vue.component('compA', {
template: '<div>JavaScript</div>'
})
Vue.component('compB', {
template: '<div>CSS</div>'
})
Vue.component('compC', {
template: '<div>HTML</div>'
})
let app = new Vue({
el: "#app",
data: {
thisView: "compA"
},
methods: {
handleView: function (tag) {
this.thisView = "comp" + tag;
}
}
})
</script>

自定义指令

类似组件可以全局注册和局部注册,使用 derective 注册。

钩子函数

指令定义函数提供了几个钩子函数(可选):

  1. bind:只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作;
  2. inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document中);
  3. update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化;通过比较更新前后的绑定值,可以忽略不必要的模板更新;
  4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用;
  5. unbind:只调用一次, 指令与元素解绑时调用。

钩子函数有以下参数:

  1. el:指令所绑定的元素,可以用来直接操作 DOM 。
  2. binding:一个对象,包含以下属性:
    1. name:指令名,不包括 “v” 前缀;
    2. value:指令的绑定值,例如 ` v­my­directive=”1 + 1”,value 的值是2;
    3. oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用;
    4. expression:绑定值的字符串形式,例如 v­my­directive="1 + 1" ,expression 的值是“1 + 1”;
    5. arg:传给指令的参数。例如 v­my­directive:foo, arg 的值是 “foo”。
    6. modifiers:一个包含修饰符的对象, 例如v­my­directive.foo.bar,修饰符对
      象 modifiers 的值是{ foo: true, bar: true }
  3. vnode:Vue 编译生成的虚拟节点。
  4. oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
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
<div id="app">
<input type="text" v-focus>获取焦点
<br>
<input type="text">
<hr>
<div v-command:expression.a.b.c="value"></div>
</div>
<script>
// 自定义指令
Vue.directive('focus', {
inserted: function (el) { // inserted 选项:插入父节点时调用
el.focus()
}
})

Vue.directive('command', {
bind: function (el, binding, vnode) {
let keys = []
for (let key in vnode) {
keys.push(key)
}
console.log(keys)
el.innerHTML =
// '<div v-command:expression=.a.b.c="value"></div>' + "<br>" +
"name: " + binding.name + "<br>" +
"name: " + binding.value + "<br>" +
"expression: " + binding.expression + "<br>" +
"argument: " + binding.arg + "<br>" +
"modifiers: " + JSON.stringify(binding.modifiers) + "<br>" +
"vnode keys: " + keys.join(", ") + "<br>"
}
})
let app = new Vue({
el: "#app",
data: {
value: "自定义指令所绑定的值"
}
})
</script>

render函数

在 render 函数中使用 this.$slotsprops 传递数据:

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
<div id="app">
<child :level="level">
标题
</child>
<button v-on:click="add1()">+1</button>
<button v-on:click="minus1()">-1</button>
</div>
<script>
Vue.component('child', {
// 如使用template则只允许有一个子节点
render: function (createElement) { // 注意,参数createElement不可修改

// createElement函数的使用:
return createElement(
// 第一个参数(必须,用于创建DOM元素,可以是String/ Object/ Function)
'h' + this.level, // String(HTML标签)
// {template: '<div>haha</div>>'}, // Object(含有数据选项的对象)
// function () { // Function(返回含有数据选项的对象)
// return {template: '<div>haha</div>>'}
// },

// 第二个参数(可选,数据对象,只能是Object)
this.$slots.default // 含有VNode的数组
// {
// class: {active: true}, // 创建的DOM元素的属性
// style: {color: red, fontSize: '16px'},
// attrs: {id: 1, src: ""},
// domProps: {innerHTML: '<span style="color: red">红色</span>'},
// on: {input: functiopn(){//...}}
// }

// 第三个参数(可选,动态的VNode虚拟节点,可以是String或Array)
// [createElement("h1", "标题"), createElement("h2", "二级标题")]
);
},
props: ['level'] // 使用 props 在 render 函数中传递数据
})

let app = new Vue({
el: "#app",
data: {
level: 1
},
methods: {
add1: function () {
if (this.level < 6)
this.level += 1;
},
minus1: function () {
if (this.level > 1)
this.level -= 1;
}
}
})
</script>

在render函数中使用v-model

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
<div id="app">
<!-- 方法1:子组件使用$emit向父组件传递事件 -->
<!--<my-component :name="name" @input="showName"></my-component>-->

<!-- 方法2:使用v-model绑定属性,且自动接收input事件从子组件传递给父组件的值 -->
<my-component :name="name" v-model="name"></my-component>
<br> {{ name }}
</div>
<script>
Vue.component("my-component", {
render: function (createElement) {
let self = this // 临时保存this,此处的this为当前Vue实例
return createElement("input", {
domProps: {
value: self.name
},
on: {
input: function (event) {
console.log(this) // 此处的this为window
self.$emit("input", event.target.value) // 输入内容传递给父组件
}
}
})
}
})

let app = new Vue({
el: "#app",
data: {
name: "ywh"

},
// methods: {
// showName: function(value) {
// this.name = value
// }
//
// }
})
</script>

在 render 函数中使用作用域插槽:

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
<div id="app">
<my-component>
<!-- 作用域插槽写在template中 -->
<template scope="prop"> <!-- prop获取传递过来的数据 -->
{{ prop.text }}
</template>
</my-component>
</div>
<script>
Vue.component('my-component', {
render: function (createElement) {

// 相当于在template中创建一个<div><slot :text="text"></slot></div>
return createElement(
'div',
this.$scopedSlots.default({ // 使用作用域插槽
text: '子组件传递过来的数据',
})
)
}
})
let app = new Vue({
el: "#app"
})
</script>

函数化组件

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
<div id="app">
<my-component value="子组件信息"></my-component>
</div>
<script>
Vue.component('my-component', {
functional: true, // 表示当前Vue实例无状态,无实例(没有this)
render: function(createElement, context) {
// context中存放父组件、子组件中的内容
// this.text === context.props.text
// this.$slots.default === context.children
return createElement("button", {
on: {
click: function() {
console.log(context)
console.log(context.parent) // 表示父组件
alert(context.parent.parentMsg) // 父组件内容(msg)
console.log(context.props.value) // 子组件内容
}
}
}, "click")
},
prope: ["value"]
})
let app = new Vue({
el: "#app",
data: {
parentMsg: "父组件信息"
}
})
</script>

vue-router

使用 vue-router 可实现前端路由。

安装 vue-router:

1
cnpm install --save vue-router

配置路由文件,并在 Vue 实例中导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import router from 'vue-router'
import HelloWorld from './components/HelloWorld'
// ...

Vue.use(router)

var rt = new router({
routes:[{
path: '/', //指定要跳转的路径
component: HelloWorld //指定要跳转的组件
}]
})
new Vue({
el: '#app',
router: router,
components: { App },
template: '<App/>'
})

设置视图(组件)加载的位置

1
<router-view></router-view>

路由跳转

1
2
3
4
5
6
7
8
9
10
11
<router-link to="/"></router-link>
<template>
<ul>
<li>
<router-link to="/helloworld">HELLO WORLD</router-link>
</li>
<li>
<router-link to="/helloearth">HELLO EARTH</router-link>
</li>
</ul>
</template>

路由参数传递

  • 必须在路由内加入路由的 name
  • 必须在path后加 /: 加上传递的参数

路由:

1
2
3
4
5
6
7
8
9
10
const router = new VueRouter({
routes: [
{
name: "helloworld",
path: '/helloworld/:msg', // 指定跳转的路径和参数
component: HelloWorld, // 指定跳转的组件
// props: (route) => ({query: route.query.q})
}]
})

使用 params 在跳转链接带上参数:

1
2
3
4
5
6
7
<router-link
:to="{name: helloearth, params: {msg: '你好世界'}}">
HELLO WORLD
</router-link>

<!-- 传递:/helloworld/你好世界 -->
<!-- 接收:{{ $route.params.msg }} -->

使用 query 在跳转链接带上参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<router-link
:to="{name: helloearth, query: {msg: '你好世界'}}">
HELLO WORLD
</router-link>

<!-- 传递:/helloworld?msg=你好世界 -->
<!-- 接收:可以创建一个函数返回 props,可将参数转换成另一种类型,将静态值与基于路由的值结合等等
const router = new VueRouter({
routes: [{
path: '/search',
component: SearchUser,
props: (route) => ({query: route.query.q })
}]
})
-->

发送 HTTP 请求

axios 是一个基于 Promise、用于浏览器和 Node.js Server 的HTTP客户端:

  1. 从浏览器中创建 XMLHttpRequest
  2. 从 Node.js Server 发出 http 请求
  3. 支持 Promise API
  4. 拦截请求和响应
  5. 转换请求和响应数据
  6. 取消请求
  7. 自动转换JSON数据
  8. 防止CSRF/ XSRF

安装 axios

1
npm install axios

导入并挂载到 Vue 原型中:

1
2
import axios from 'axios'
Vue.prototype.$http = axios;

发送 Get 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getData(){
var self = this;
this.$http.get('https://cnodejs.org/api/v1/topics')
.then(function (res) {
// 此处的this指向的不是当前vue实例
self.items = res.data.data
})
.catch(function (err) {
console.log(err)
})
}

axios.get('/user',
{params: {ID: 12345}}
)
axios.get('/user', {
ID: 12345
})
axios.get('https://cnodejs.org/api/v1/topics?page=1&limit=15')
// 以CNODE社区官方的API为例
// 获取主题列表API:https://cnodejs.org/api/v1/topics
// 参数:page页码,limit 每页显示的数量

发送 Post 请求(有两种格式):

  • form-­data:?page=1&limit=48
  • x­-www­-form-­urlencoded:{page: 1, limit: 10}

在 axios 中,Post 请求接收的参数必须是 form­-data,因此需要安装 qs 插件

1
cnpm install qs
1
2
3
4
5
6
7
8
9
10
11
12
13
postData(){
var self = this;
this.$http.post(url, qs.stringify({
page:1,
limit:10
}))
.then(function (res) {
self.items = res.data.data
})
.catch(function (err) {
console.log(err)
})
}

状态管理

使用 vuex 可实现状态管理(在各个组件之间管理外部状态),共享数据。

安装 vuex:

1
cnpm install vuex

父子组件之间数据传递

child.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
<template>
<div>
<span>子组件</span>:{{ fromParentMsg }}
<button @click="sendMsgToParent">向父组件传递消息</button>
</div>
</template>
<script>
export default {
name: "child",
props: {
fromParentMsg: {
type: String, default: ""
}
},
data: function() {
return {toParentMsg: "子组件向父组件传递消息"}
}
methods: {
sendMsgToParent: function () {
this.$emit('handle', this.toParentMsg) // 发生handle事件,向父组件发送消息
}
}
}
</script>

parent.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
<template>
<div>
<span>父组件</span>:{{ fromChildMsg }}
<hr>
<child :fromParentMsg="toChildMsg" @handle="getMsgFromChild"></child>
</div>
</template>

<script>
import child from './child' // 在父组件中引入子组件

export default {
name: "parent",
data: function() {
return {toChildMsg: "父组件向子组件传递消息", fromChildMsg: ""}
},
components: {child},
methods: {
getMsgFromChild: function(value) {
this.fromChildMsg = value
}
}
}
</script>

多个组件之间共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Vuex from 'vuex'
Vue.use(Vuex)

// 创建状态仓库,注意store、state不能修改
var store = new Vuex.Store({
state: { // 存放定义的状态
name: ywh
}
})

new Vue({
el: "#app",
router,
store,
components: {APP},
template: "<App/>"
})

// 各组件直接通过 `this.$store.state.name` 拿到全局状态

vuex 操作

vuex 状态管理的流程:

  1. view
  2. actions
  3. mutations
  4. state
  5. view

实现状态修改:

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var store = new Vuex.Store({
state: { // 存放定义的状态
name: ywh
},
mutations: { // 改变状态
reverse(state) {
state.name.split("").reverse().join("")
}
},
actions: { // actions可以包含异步操作,mutation只能包含同步操作
reverseAction(contenxt) { // 传入上下文对象
setTimeout(function () {
context.commit("reverse"); // 只对mutation操作,不直接修改状态
}, 1000)
}
},
getters: {
getName(state) {
return state.name.length > 2 ? state.name : ""
}
} // 定义getter方法,使外部获取状态不需要通过this.$store.state.name直接访问变量
// this.$store.getters.getName
})

parent.vue

1
2
3
4
5
6
7
8
9
<button @click="reverse"></button>

// ...
methods: {
reverse() {
this.$store.commit('reverse')
// this.$store.dispatch('reverse') 通过actions调用
}
}
CATALOG
  1. 1. Vue 学习笔记
    1. 1.1. 与 jQuery 对比
    2. 1.2. 生命周期
    3. 1.3. 文本插值,表达式
    4. 1.4. 过滤器
    5. 1.5. 计算属性
      1. 1.5.1. 监听属性变化
    6. 1.6. 基本指令
      1. 1.6.1. v-bind
      2. 1.6.2. v-cloak
      3. 1.6.3. v-once
      4. 1.6.4. 条件渲染指令:v-if,v-else-if,v-else
      5. 1.6.5. 条件渲染指令:v-show
      6. 1.6.6. 列表渲染指令:v-for
    7. 1.7. 方法与事件
      1. 1.7.1. 数组更新
      2. 1.7.2. 修饰符
      3. 1.7.3. 键盘事件
    8. 1.8. 表单与 v-model
      1. 1.8.1. 输入框和编辑框
      2. 1.8.2. 单选按钮
      3. 1.8.3. 复选框
      4. 1.8.4. 下拉框
      5. 1.8.5. 绑定值
    9. 1.9. 组件
      1. 1.9.1. 命名规则
      2. 1.9.2. 组件注册
    10. 1.10. 数组验证
    11. 1.11. 组件通信
      1. 1.11.1. 父组件向子组件传递数据
      2. 1.11.2. 子组件向父组件传递数据
      3. 1.11.3. 非父组件之间的通信
      4. 1.11.4. 使用 Slot 分发内容
        1. 1.11.4.1. 编译的作用域
        2. 1.11.4.2. 插槽的用法
        3. 1.11.4.3. 作用域插槽
        4. 1.11.4.4. 访问插槽
      5. 1.11.5. 动态组件
    12. 1.12. 自定义指令
      1. 1.12.1. 钩子函数
    13. 1.13. render函数
    14. 1.14. 函数化组件
    15. 1.15. vue-router
    16. 1.16. 路由跳转
    17. 1.17. 路由参数传递
    18. 1.18. 发送 HTTP 请求
    19. 1.19. 状态管理
      1. 1.19.1. 父子组件之间数据传递
      2. 1.19.2. 多个组件之间共享数据
      3. 1.19.3. vuex 操作