基础精讲
# Vue 实例生命周期函数
生命周期函数就是 Vue 实例在某个时间点自动执行的函数,一共有 8 个。Vue 的生命周期非常重要,如果无法理解,建议学完其内容再来学习!
组件创建时会被执行
- beforeCreate
- created 一般放跟 数据 相关的东西
组件渲染模板内容的时候会被执行
- beforeMount
- mounted 一般放跟 DOM 节点 相关的东西
只有数据改变时才能执行
- beforeUpdate
- updated
当组件被销毁时会被执行
- beforeDestroy
- destroyed
var vm = new Vue({
el: "app",
template: "<div>{{ content }}</div>",
data: {
content: "hello World"
},
beforeCreate: function() {
console.log("beforeCreate");
},
created: function() {
console.log("created");
},
beforeMount: function() {
console.log(this.$el);
console.log("beforeMount");
},
mounted: function() {
console.log(this.$el); // 此时模板渲染完成
console.log("mounted");
},
beforeUpdate: function() {
console.log("beforeUpdate");
},
updated: function() {
console.log("updated");
},
beforeDestroy: function() {
console.log("beforeDestroy");
},
destroyed: function() {
console.log("destroyed");
}
});
- 在开发过程中,更多的是使用 created,mounted,updated,destroyed 等四个生命周期钩子
- created 钩子在页面中执行顺序 extends -> mixins- > comp -> child-comp 而 mounted 钩子则反过来执行
在 beforeMount 和 mounted 之间
- render .vue 文件是 使用 vue-loader 进行解析的
- renderError 只能用于生产环境
- errorCaptured 会向上冒泡,并且正式环境可以使用
其实更准确来说,Vue 中应该有 10 个生命周期,在使用 组件 keep-alive
后,便多出两个生命周期钩子 activated
和 deactivated
# computed
computed
是 Vue 提供的计算属性,简单看下例子
<div id="app">
{{ fullName }}
</div>
var vm = new Vue({
el: '#app',
data: {
firstName: 'Zero',
lastName: 'King'
}
computed: {
fullName: function(){
return this.firstName + '' + this.lastName
}
}
})
注意
计算属性 computed 是内置缓存的,只有它依赖的值发生改变才会进行数据的刷新。
以上的方式也可以使用监听器 watch 的方式来进行改变,只不过比较冗余,分别监听 firstName 和 lastName 的值
var vm = new Vue({
el: '#app',
data: {
firstName: 'Zero',
lastName: 'King',
fullName: 'Zero King'
}
watch: {
firstName: function(){
this.fullName = this.firstName + '' + this.lastName
},
lastName: function(){
this.fullName = this.firstName + '' + this.lastName
}
}
})
如果需要实现的功能既可以使用 computed,也可以使用 watch 和 method 方法来实现,那应该优先选择既简洁又方便的 computed
# 计算属性的 setter 和 getter
通过 gettter 方法来赋值,使用 setter 来改变 computed 所依赖的值
<div id="app">
{{ fullName }}
</div>
var vm = new Vue({
el: '#app',
data: {
firstName: 'Zero',
lastName: 'King'
}
computed: {
fullName: {
get: function(){
return this.firstName + '' + this.lastName
},
set: function(val){
console.log(val)
var arr = val.split(' ')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
如果 computed 后的数据为“引用类型”,其实是可以直接运用“引用类型”的副作用进行数据修改的,但并不推荐这样做,computed 出来的数据应该只作为“只读”状态才对。
# watch
watch
顾名思义,就是“监视,监听”的意思。在开发的过程中,我们常常需要监听一个对象的变化来做对应的处理,所以需要经常使用到这个钩子
一般可以用以下写法来调用 watch 钩子
var vm = new Vue({
el: '#app',
data: {
firstName: 'Zero',
lastName: 'King',
fullName: 'Zero King',
time: [1603545150055, 1603546150055],
form: {
field: 'formField',
inner: {
field: 'innerField'
}
}
}
watch: {
firstName() {
this.fullName = this.firstName + '' + this.lastName
},
lastName() {
this.fullName = this.firstName + '' + this.lastName
}
}
})
但比如,我要在初始化的时候
- 只想监听
form.field
的改变,watch 钩子能够监听到吗? - 修改
form.inner.field
时,watch 钩子能够监听到吗? - 想直接修改数组
time
里某个元素的值,watch 钩子能够监听到吗?
答案是可以的,需要借助到 immdiate 和 deep 这两个选项来监听 form
,需要借助 Vue.set() 方式来更改 time
所以根据以上的提问,需要编写的 watch 如下:
var vm = new Vue({
el: "#app",
data: {
firstName: "Zero",
lastName: "King",
fullName: "Zero King",
time: [1603545150055, 1603546150055],
form: {
field: "formField",
inner: {
field: "innerField"
}
}
},
watch: {
firstName() {
this.fullName = this.firstName + "" + this.lastName;
},
lastName() {
this.fullName = this.firstName + "" + this.lastName;
},
form: {
handler() {
console.log("form", this.form);
},
immediate: true,
deep: true
},
"form.filed"() {
console.log("field", this.form.filed);
},
time() {
console.log("time", this.time);
}
},
mounted() {
this.form.inner.filed = "mountedField";
// 这样无法同步到视图层中进行展示,要使用 ES5 的方法对数组进行更新才行,或者使用 $set 的方式
// this.time[1] = 1603546671483
this.$set(this.time, 1, 1603546671483);
}
});
# Vue 的条件渲染
<div id="app">
<div v-if="show">
{{ message }}
</div>
<div v-else>
false
</div>
<div v-show="show">
{{ message }}
</div>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true,
message: "Hello World"
}
});
</script>
v-if 和 v-show 的区别
在模板进行渲染的时候使用 v-show
会将 DOM 节点渲染出来,然后使用 css 进行隐藏。而 v-if
则是根据条件,再动态地进行 DOM 节点的渲染。
注意
Vue 在使用 v-if 的时候会尝试复用页面上已经存在的 DOM,所以如果出现这样的问题,给 被复用的节点 设置 key
属性。这是 Vue 虚拟 DOM 的一个小 Bug
# 列表渲染
<div id="app">
<!-- 注意这个 key 值需要唯一,其实尽量不要用 index 来做 key 值,
除非数组是不变的。在一般的开发中应该用后台传过来的唯一键。 -->
<div v-for="(item, index) of list" :key="index">
{{ item }} --- {{ index }}
</div>
<div v-for="(item, key, index) of user">
{{ item }} --- {{ key }} --- {{ index }}
</div>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
list: ["Hello", "World", "2333"],
user: {
name: "zking",
age: 24
}
}
});
</script>
修改数组后,Vue 无法动态显示?
开发者不能直接使用数组的下标进行数组内容的增删改查,这样虽然能更改数据,但不能同时改变页面的内容。Vue 提供了三种解决方案
- 一种是使用 ES5 中的数组方法来对数组进行修改,如
splice,push
等 - 还有一种是改变数据的引用地址,即重新为
list
赋值。 - 另外一种是使用 Vue 的
set
方法Vue.set(vm.list, [index], [val])
vm.$set(vm.list, [index], [val])
:::
为对象增加属性名后,Vue 无法动态显示?
Vue 中可以直接通过对象的属性名进行动态的修改,但不能动态地增删。Vue 提供了两种解决方案
- 一种是改变数据的引用地址,即重新为
user
赋值来进行对象属性名的增删操作。 - 另外一种是使用 Vue 的
set
方法Vue.set(vm.user, 'sex', ’male')
vm.$set(vm.user, 'sex', ’male')
:::
# 组件使用的细节点
使用 is
属性
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component("row", {
data: function() {
return {
content: "this is row"
};
},
template: "<tr><td>{{ content }}</td></tr>"
});
var vm = new Vue({
el: "#app"
});
</script>
如果不使用
<tr is = 'row'></tr>
,而是直接使用<row></row>
则会出现 DOM 层级渲染不正确的 bug,Vue 提供使用is
属性来解决这个问题另外,在根组件定义 data 时,可以使用对象来定义,但是在子组件定义 data 只能用函数来定义
# 使用 ref 在 Vue 中操作 DOM
有时一些功能真的没有办法直接使用 Vue 的内置方法,而是只能操作 DOM 来实现功能。这种时候 Vue 提供了一个 ref
的属性,然后使用 $refs.[name]
来操作
<div id="app">
<div ref="hello" @click="handleClick">
hello world
</div>
</div>
<script>
var vm = new Vue({
el: "#app",
methods: {
handleClick: function() {
alert(this.$refs.hello.innerHTML); // hello world
}
}
});
</script>
# 系统地了解父子组件的数据传递
<div id="app">
<counter :count="0" key="counterA" @change="handleChange"></counter>
<counter :count="1" key="counterB" @change="handleChange"></counter>
<p>{{ total }}</p>
</div>
<script>
var counter = {
props: ["count"],
data: function() {
return {
number: this.count
};
},
template: '<div @click="handleClick">{{ number }}</div>',
methods: {
handleClick: function() {
this.number += 1;
this.$emit("change", this.number);
}
}
};
var vm = new Vue({
el: "#app",
data: {
total: 1
},
components: {
counter: counter
},
methods: {
handleChange: function(step) {
this.total += 1;
}
}
});
</script>
- 父组件通过属性 props 的形式向子组件传递数据
- 在 Vue 中有单向数据流的概念,即父组件可以随意修改传递给子组件的参数,而子组件不能去修改父组件传递过来的参数。即 counter 不能去改变 count 的值,但是 counter 可以自己再定义 data 来复制 count,然后再修改这个副本数据。
- 子组件可以通过事件触发 $emit 的方式与父组件通信,同时,父组件对该事件进行监听。
# 组件参数校验
父组件可以传递数据给子组件,子组件可以对传递过来的数据进行约束,可以对上述的例子进行修改。这里只显示 props 修改的部分
可以将 props 改为一个对象,而不是数组来进行数据的传递
...
props: {
count
}
...
props 中的属性名还可以进行简单的类型约束
...
props: {
// count: Number
// count: [Number,String]
count: {
type: Number
}
}
...
不仅如此,还可以使用各种条件来进行约束
...
props: {
count: {
type: Number,
required: false,
default: '0',
validator: function(val){
return val >= 0 // 要求传入的参数要 大于或等于 0
},
}
}
...
# 给组件绑定原生事件
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component("child", {
template: "<div>Hello Child</div>"
});
var vm = new Vue({
el: "#app",
methods: {
handleClick: function() {
alert("child click");
}
}
});
</script>
上面这个例子,想要做到的效果是,直接给组件绑定 Click 事件,然后点击Hello Child
后显示弹出框,但是并没有效果的。对此有两种解决方案
- 一种是使用父子组件通信的方式来触发,但是过于繁琐
- 另外一种,则是使用 .native 修饰符,直接修改为
<child @click.native = 'handleClick'></child>
。这样就能给组件绑定原生事件
# 非父子组件传值的问题
有两种解决方案
- 一种是使用发布-订阅模式 / 观察者模式,Vue 中称为总线机制
- 另外一种是使用 Vuex,Vuex 的内容会在之后再进行讲解
这里主要讲第一种方案
<div id="app">
<child content="Zero"></child>
<child content="King"></child>
</div>
<script>
// 将 bus 挂载到 Vue 的原型上,并且将 bus 赋值为一个 Vue 的实例
Vue.prototype.bus = new Vue();
Vue.component("child", {
props: {
content: String
},
data: function() {
return {
selfContent: this.content
};
},
template: '<div @click="handleClick">{{ selfContent }}</div>',
methods: {
handleClick: function() {
console.log(this.selfContent);
this.bus.$emit("change", this.selfContent);
}
},
mounted: function() {
// 在渲染 DOM 时进行数据监听
var that = this;
this.bus.$on("change", function(val) {
// 注意,这里 this 的指向已经改变了
that.selfContent = val;
});
}
});
var vm = new Vue({
el: "#app"
});
</script>
# 在 Vue 中使用插槽 slot
<div id="app">
<child>
<p>这里是 slot 的用法</p>
</child>
</div>
<script>
Vue.component("child", {
template: `<div>
<p>Hello Child</p>
<slot></slot>
</div>`
});
var vm = new Vue({
el: "#app"
});
</script>
上述是单个插槽的用法,但如果要传多个插槽呢?这个时候就要使用具名插槽,而且插槽是可以设置默认值的
<div id="app">
<child>
<p slot="header">Header</p>
<p slot="footer">Footer</p>
</child>
</div>
<script>
Vue.component("child", {
template: `<div>
<slot name = 'header'>default header</slot>
<p>Hello Child</p>
<slot name = 'footer'>default footer</slot>
</div>`
});
var vm = new Vue({
el: "#app"
});
</script>
当子组件做循环,或者子组件中的某一部分 DOM 需要通过父组件来进行传递时,就应该使用作用域插槽
<div id="app">
<child>
<!-- 这里一定要使用 template, 这是一个固定写法 -->
<template slot-scope="props">
<li>{{ props.item }}</li>
</template>
</child>
</div>
<script>
Vue.component("child", {
data: function() {
return {
list: [1, 2, 3, 4]
};
},
template: `<div>
<ul>
<slot v-for='item of list' :item = item></slot>
</ul>
</div>`
});
var vm = new Vue({
el: "#app"
});
</script>
# 动态组件和 v-once 指令
如果想要实现一个 toggle 交换显示组件的效果,可以使用以下的写法
<div id="app">
<child-one v-if=' type === "child-one" '></child-one>
<child-two v-if=' type === "child-two" '></child-two>
<button @click="handleClick">
change
</button>
</div>
<script>
Vue.component("child-one", {
template: "<div>child-one</div>"
});
Vue.component("child-two", {
template: "<div>child-two</div>"
});
var vm = new Vue({
el: "#app",
data: {
type: "child-one"
},
methods: {
handleClick: function() {
this.type = this.type === "child-one" ? "child-two" : "child-one";
}
}
});
</script>
但是这种写法稍微繁琐和耗费一定的性能开销,所以 Vue 提供了两种方案
- 一种时使用动态组件,减少繁琐的写法
<child-one v-if=' type === "child-one" '></child-one>
<child-two v-if=' type === "child-two" '></child-two>
<!-- 将以上内容替换成-->
<component :is="type"></component>
- 使用动态组件其实还是会耗费一定的性能的,所以另外一种方案就是使用
v-once
指令来减少性能开销
// 修改这个部分,其余不变
Vue.component("child-one", {
template: "<div v-once>child-one</div>"
});
Vue.component("child-two", {
template: "<div v-once>child-two</div>"
});