Vue生命周期函数
生命周期函数是指,在某一时刻会自动执行的函数。(生命周期也叫钩子函数)
学习记忆时可以成对进行记忆,也就是分为四组。
- beforeCreate( )
- 在初始化 / 实例创建之前执行的函数
- Created( )
- 在初始化 / 实例创建之后执行的函数
- beforeMount( )
- 在组件内容被渲染到页面之前自动执行的函数
- 注意:此时无法找到任何模板DOM节点
- Mounted( )
- 在组件内容被渲染到页面之后自动执行的函数
- beforeUpdate( )
- 在数据将要变化之前自动执行的函数
- updated( )
- 在数据发生变化之后自动执行的函数
- beforeUnmount( )
- 在VUE实例销毁之前自动执行的函数
- unmounted( )
- 在VUE实例销毁之后自动执行的函数
通过下面案例帮我们学习整个调用周期运转。在控制台查看各函数的调用顺序,并调用destroy,再点改变title按钮,发现改变不了,因为已被销毁
<html>
<head>
<meta charset="UTF-8">
<title>生命周期</title>
</head>
<body>
<div id="app1">
{{title}}
<button type="button" @click="changeTitle">change title</button>
<button type="button" @click="destroy">destroy</button>
</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
<script>
new Vue({
el: "#app1",
data: {
title: "this is title"
},
methods: {
changeTitle: function () {
this.title = "new title";
},
destroy: function () {
this.$destroy();
}
},
beforeCreate() {
console.log("beforeCreate")
},
created() {
console.log("created")
},
beforeMount() {
console.log("beforeMount")
},
mounted() {
console.log("mounted")
},
beforeUpdate() {
console.log("beforeUpdate")
},
updated() {
console.log("updated")
},
beforeDestroy() {
console.log("beforeDestory")
},
destroyed() {
console.log("destory")
}
})
</script>
</html>
Vue实例知识点
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<h1>
{{title}}
</h1>
<button id="btn" @click="btnclick">show</button>
<p v-if="showFlag">显示段落</p>
{{lowercasetitle}}
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
//一个Vue实例
var v1 = new Vue({
el:'#app',
data:{
title:"测试标题",
showFlag:false
},
methods:{
btnclick:function(){
this.showFlag=true;
this.updateTitle("修改后的标题");
},
updateTitle:function(d){
this.title = d;
}
},
computed:{
lowercasetitle:function(){
return this.title.toLowerCase();
}
},
watch:{
title:function(newVal,oldVal){
console.log(newVal)
}
}
})
</script>
</html>
新的实例 ,可以创建多个实例对象
new Vue({
el:"#app2"
})
假如我们要在实例一(#app1)中改变实例二(#app2)中的内容 需求如下:
在第一个Vue实例对象里面改变第二个Vue实例对象的 name 值
在第二个Vue实例对象里面改变第一个Vue实例对象的 name 值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<h1>app里面的数据</h1>
<h2>{{name}}</h2>
<button type="button" @click="clickApp">转换app2</button>
</div>
<hr />
<div id="app2">
<h1>app2里面的数据</h1>
<h2>{{name}}</h2>
<button type="button" @click="clickApp2">转换app1</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
//第一个Vue实例
var vm1 = new Vue({
el:'#app',
data:{
name:'chen'
},
methods:{
clickApp:function(){
vm2.name = "chen"
}
}
})
var vm2 = new Vue({
el:'#app2',
data:{
name:'chenjia'
},
methods:{
clickApp2:function(){
vm1.name = "chenjia"
}
}
})
</script>
</html>
在vue外面操作vue实例——操作属性
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<ul>
<h2>{{name}}</h2>
</ul>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
var vm1 = new Vue({
el:'#app',
data:{
name:'chen'
}
})
setTimeout(function(){
vm1.name="sttitle";
},2000);
</script>
</html>
调用vue实例中的方法——操作方法
<!--html代码-->
<div id="app">
<h2>{{count}}</h2>
</div>
<!--Js代码-->
<script type="text/javascript">
var vm1 = new Vue({
el:'#app',
data:{
count:0
},
methods:{
addcount:function(){
this.count++
}
}
})
setTimeout(function(){
vm1.addcount()
},2000);
</script>
实例属性ref
的用法:相当于是id
在vue里面,往往使用ref属性来代替id属性的使用。那么就可以快速的通过调用ref的值来获得页面中的某个元素
<div id="app">
<button ref="btn1" id="btn" @click="btnclick">show</button>
<button ref="btn2" id="btn" @click="btnclick">show</button>
</div>
<!--Js代码-->
<script type="text/javascript">
var vm1 = new Vue({
el:'#app',
data:{
count:0
},
methods:{
btnclick:function(){
this.$refs.btn1.innerText = "修改btn";
}
}
})
</script>
动态绑定vue实例到页面中
实现了页面的元素和vue对象的动态绑定,之前都是通过el的方式来绑定,也可以通过mount实例属性进行
<div id="app3"></div>
<script>
var v3 = new Vue({
template:"<h1>hello</h1>"
});
v3.$mount("#app3");
</script>
Vue组件化开发
本章主要涉及的知识点如下。
- 组件概念
- 组件注册
- 组件通信
- 插槽
- 特殊情况
什么是组件化开发
我们面对一个复杂问题时,不太可能一次性搞定所有的内容,我们可以将问题拆解,拆解成一个一个小问题,再将这些小问题一个一个解决。组件化开发就是类似的思想。
如果我们将一个页面中所有的逻辑都放到一起,处理起来会非常的复杂,而且不利于后续的管理和扩展。
但是如果我们将一个页面拆分成一个一个的小功能块,每个功能块完成属于自己的这部分独立的功能,那么之后整个页面管理和维护都变得非常容易。
组件化和模块化的不同:
- 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
- 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;
总之,此功能是Vue.js框架中最精彩的部分之一,运用得当可以在很大程度上减少重复代码量,页面结构也会变得简洁
为什么要使用组件
为什么要使用组件?很多人在刚开始学习组件时可能都会问这个问题,因为组件是有学习成本的。若是学习成本很高,但实际效果令人也不满意,那么学这个东西就是不值得的。先举一个例子,如图下图所示。
一个很简单的搜索框,在很多项目中都会用到,而且有很大概率不止使用一次,可能几个页面都有相同的搜索框。那么问题来了,是为了方便每次都使用复制和粘贴功能,还是封装成一个组件呢?例如遇到搜索框的地方就使用复制、粘贴实现,其实是比较方便的,毕竟每次不用思考,可以很快地完成开发。但是会不会有这样一种情况,产品经理突然提了一个需求:给每个 <input>
标签都绑定上回车事件,按Enter键,<input>
标签中的内容会自动提交。这种情况可能有很多页面都需要改动,需要很长时间才能完成所有回车事件的触发。若是使用组件呢?这种问题就不是问题了,可以直接给组件绑定回车事件,如此便不用一个个修改了,便可以完成相应的需求了。
这就是为什么学习组件——让代码的复用性更强,同时对代码进行解耦。
什么是解耦?就是降低代码的耦合度。那耦合度又是什么?就是代码之间的联系。若是两个函数的联系很紧密,或者说一个函数只能为另外一个函数所调用,那么可以说,这两个函数的耦合度很高。反之,一个函数可以被很多个函数调用,那么这个函数的耦合性很低。
耦合度低有什么好处呢?首先就是代码量大大减少,其次就是函数和代码看起来会更加清晰,每个函数不相互依存,功能性更加突出。如此,不管是日后调试还是二次开发,都会十分方便,错误的定位也会更加简单。
这就是学习组件的目的,通过多种基础组件的组合形成完整的功能,同时功能的存在又不会增加多余的代码,如此修改,也会更加方便,牵一发而动全身。缺点就是对相应功能的规范要求较高,若是不符合规范,则会增加很多代码,所以,最好将常用的简单部分开发为组件,若是某个部分不常用,则无须组件化,增加工作强度。
Vue组件化思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构建我们的应用。
任何的应用都会被抽象成一颗组件树。
有了组件化的思想,我们在之后的开发中就要充分的利用它。尽可能的将页面拆分成一个个小的、可复用的组件。这样让我们的代码,更加方便组织、管理、扩展。
组件基础练习示例
定义一个名为 button-counter 的新组件。每一次单击按钮,按钮上的数字都会加1。组件内部定义了count变量,之后在<button>
标签上绑定了单击事件,每一次单击,count变量会自动增1。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="counter-demo">
<button-counter></button-counter>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 注册一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#counter-demo' })
</script>
</html>
分析:
注册一个全局组件语法格式如下:
Vue.component(tagName, options)
tagName
为组件名,options
为配置选项。
⭐ 注册后,我们可以使用以下方式来调用组件:
<tagName></tagName>
组件命名的规则(tagName):
- kebab-case
需要注意的是组件的名字,这里给组件命名为button-counter,标签的名字也是如此。这里使用短横线分隔命名(kebab-case)。当使用短横线命名法命名组件时,调用组件时也必须使用短横线命名法,就像例子中的一样。 - PascalCase
另外一种更为常用的命名法,那就是驼峰命名法(PascalCase)。上面例子中的组件就可以命名为BttonCounter。调用的时候,可以使用驼峰命名法<BttonCounter>
或者短横线命名法<button-counter>
来命名。相对于短横线命名法来说,驼峰命名法使用范围更广。虽然可以使用两种调用方式,但是建议统一命名方式和调用方式,否则,在搜索组件名时,没有调用结果的情况,就十分尴尬了。
注意:还有一点需要注意,在新建组件的时候,data属性必须是一个函数,就像上面的例子一样,如果不是一个函数(如下所示):
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
在组件多次调用的时候,它们的值会相互覆盖,就是改变一个值,其他值都会受到影响。例如:
组件复用
这里多次引用button-counter这个组件,如果新建组件时,data属性是一个对象,而不是函数,单击任意一个按钮,其他按钮的值就会改变。若是data属性是一个函数,它们会维护自己data中的数据,不会互相影响。
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
注意:当点击按钮时,每个组件都会各自独立维护它的 **count**
。因为你每用一次组件,就会有一个它的新 **实例** 被创建。
组件注册
组件注册主要分为两部分:全局注册和局部注册。
可以通过全局注册和局部注册的方式来注册一个 Vue 组件,两种方式的区别在于,全局注册的组件能够在任何地方使用,其实就是所有的 Vue 实例化的时候都会去渲染这个全局组件;
而局部组件只是注册在某一个 Vue 实例化对象上,只能在这个 Vue 实例化的时候会渲染,其他地方使用会被当成普通的Html标签渲染。我们就先来了解下全局组件的注册。
全局注册
通过Vue.component(tagName , options)
方法注册的都是全局组件,也就是他们再注册之后可以用在任何新创建的Vue
实例挂载的区域内。
tagName 为组件名,options 为配置选项。注册后,我们可以使用以下方式来调用组件:
<tagName></tagName>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app" style="border: 1px solid blue;">
<my-con></my-con>
<div style="border: 1px solid red;">
<my-con></my-con>
</div>
</div>
<div id="app1">
<!-- 组件未注册 -->
<my-con></my-con>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-con', {
template: '<section><h2>组件标题</h2><p>组件内容</p></section>'
})
const vm = new Vue({
el: '#app'
})
</script>
</html>
上面在<div id="app">...</div>
外的组件 my-con
没有替换成组件真正的页面结构,是因为 new Vue()
挂载在 id=app
的节点上,不在这个节点上标签,不会受到Vue的影响。
注意:全局注册必须在根Vue实例之前创建,需要在根实例之内使用
<div id="app">
<p>这是p标签</p>
<my-compoment></my-compoment>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 全局组件
Vue.component('my-compoment',{
template: '<div>第一个自己的全局组件</div>'
});
// 根实例
new Vue({
el:'#app',
data: {}
});
</script>
局部注册
Vue实例选项components
包含了一个属性,键是组件的名称,值是一个对象,包含了组件的模板等属性。
- 定义局部组件: const Counter={…}以变量的形式定义局部组件。
- 使用时需要进行注册在Vue 实例中后才能使用,components:{xxx:xxx}
- 注册后即可在页面使用组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<my-con></my-con>
<div>
<my-con></my-con>
</div>
</div>
<div id="app1">
<my-con1></my-con1>
</div>
</body>
<template id="template1">
<section>
<h3>组件标题</h3>
<p>组件内容</p>
</section>
</template>
<template id="template2">
<section>
<h3>组件标题B</h3>
<p>组件内容B</p>
</section>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
var componentA = {
template: '#template1'
}
var componentB = {
template: '#template2'
}
const vm = new Vue({
el: '#app',
components: {
'my-con': componentA
}
})
const vm1 = new Vue({
el: '#app1',
components: {
'my-con1': componentB
}
})
</script>
</html>
组件之间的参数传递
Prop
prop
是子组件用来接受父组件传递过来的数据的一个自定义属性。
父组件的数据需要通过props
把数据传给子组件,子组件需要显式地用props
选项声明 prop
:
下方案例是父组件通过props
传递参数到子组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<child message="hello word!"></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>
</html>
动态Prop
类似于用v-bind
绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<div>
<input v-model="parentMsg">
<hr>
<child v-bind:message="parentMsg"></child>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app',
data: {
parentMsg: '父组件内容'
}
})
</script>
</html>
注意: prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。
Prop 验证
组件可以为 props 进行验证。为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的 对象,而不是一个字符串数组。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
自定义事件
父组件是使用props
传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!
我们可以使用v-on
绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
- 使用
$on(eventName)
监听事件 - 使用
$emit(eventName)
触发事件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
//定义组件
Vue.component('button-counter', {
template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementHandler: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
</script>
</html>
注意:组件中的data 必须是一个函数,这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例,如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="components-demo3" class="demo">
<button-counter2></button-counter2>
<button-counter2></button-counter2>
<button-counter2></button-counter2>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
var buttonCounter2Data = {
count: 0
}
Vue.component('button-counter2', {
/*
data: function () {
// data 选项是一个函数,组件不相互影响
return {
count: 0
}
},
*/
data: function () {
// data 选项是一个对象,会影响到其他实例
return buttonCounter2Data
},
template: '<button v-on:click="count++">点击了 {{ count }} 次。</button>'
})
new Vue({ el: '#components-demo3' })
</script>
</html>
在组件上使用v-model
组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件。
<input v-model="parentData">
等价于:
<input
:value="parentData"
@input="parentData = $event.target.value"
>
以下实例自定义组件 runoob-input,父组件的 num 的初始值是 100,更改子组件的值能实时更新父组件的 num:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<runoob-input v-model="num"></runoob-input>
<p>输入的数字为:{{num}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.component('runoob-input', {
template: `
<p> <!-- 包含了名为 input 的事件 -->
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</p>`,
props: ['value'], // 名为 value 的 prop
})
new Vue({
el: '#app',
data: {
num: 100,
}
})
</script>
</html>
由于 v-model 默认传的是 value,不是 checked,所以对于复选框或者单选框的组件时,我们需要使用 model 选项,model 选项可以指定当前的事件类型和传入的 props。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<base-checkbox v-model="lovingVue"></base-checkbox>
<div v-show="lovingVue">
如果选择框打勾我就会显示。
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 注册
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change' // onchange 事件
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>`
})
// 创建根实例
new Vue({
el: '#app',
data: {
lovingVue: true
}
})
</script>
</html>
实例中 lovingVue 的值会传给 checked 的 prop,同时当 触发 change 事件时, lovingVue 的值也会更新。