一、Vue 实例的生命周期
1.1 什么是生命周期?
Vue 的实例具有生命周期,Vue 的实例在生成的时候,会经历一系列的初始化的过程;数据的监听,编译模板,实例挂载 DOM 元素,或者数据更新导致 DOM 更新,在执行的过程中,会运行一些叫做生命周期的钩子函数,在 Vue 实例生命周期中特定的时间点执行的函数称为生命周期的钩子函数;
如果我们需要在某个生命周期处理一些事情,我们可以把这些事情写在钩子函数中;等到 Vue 的实例生命周期到这个阶段就会执行这个钩子,而我们要做的事情也就得以处理了;
- 生命周期的钩子函数不能人为的控制其执行的顺序;
1.2 常用的生命周期
- beforeCreate 在实例初始化之后,数据观测 (data observer) 和 watch 配置之前被调用。
- created 在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算、watch/event 事件回调;但是在现阶段还没有开始挂载,即还没挂载到根 DOM 元素上,所以 this.$el 属性不可见
- beforeMount 在挂载开始之前被调用,创建虚拟 DOM(Virtual-DOM);虚拟 DOM 不是真实的 DOM 元素,而是 js 对象,其中包含了渲染成 DOM 元素信息;
- mounted 把 Vue 的虚拟 DOM 挂载到真实的 DOM 上;如果要在 Vue 中获取 DOM 元素对象,一般在这个钩子中获取;项目中的 ajax 请求一般会在这里或者 created 里发送;
- beforeUpdate 只有当数据发生变化时,才会触发这个函数;
- updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用 updated。
- beforeDestroy 在 Vue 的实例被销毁之前调用,如果页面中有定时器,我们会在这个钩子中清除定时器;
- destroyed Vue 实例销毁后调用,实例中的属性也不再是响应式的,watch 被移除
1.3 示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div @click="fn">{{msg}}</div>
</div>
<script src="vue.js"></script>
<script>
// 生命周期:
// Vue 的实例具有生命周期,Vue 的实例在生成的时候,会经历一系列的初始化的过程;数据的监听,编译模板,实例挂载DOM元素,或者数据更新导致 DOM 更新,在执行的过程中,会运行一些叫做生命周期的钩子函数,在 Vue 实例生命周期中特定的时间点执行的函数称为生命周期的钩子函数;
// 如果我们需要在某个生命周期处理一些事情,我们可以把这些事情写在钩子函数中;等到 Vue 的实例生命周期到这个阶段就会执行这个钩子,而我们要做的事情也就得以处理了
// 生命周期的钩子函数不能人为的控制其执行的顺序;
let vm = new Vue({
data: {
msg: 'hello'
},
methods: {
fn() {console.log(11111)}
},
beforeCreate() {
// 在实例初始化之后,数据观测 (data observer) 和 watch 配置之前被调用。
console.log(1);
console.log(this.msg);
console.log(this.$el); // this.$el 是根 DOM 元素
},
created() {
// 在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算、watch/event 事件回调
// 但是在现阶段还没有开始挂载,即还没挂载到根 DOM 元素上,所以 this.$el 属性不可见
console.log(2);
console.log(this.msg);
console.log(this.$el);
},
beforeMount() {
// 在挂载开始之前被调用,创建虚拟DOM(Virtual-DOM);虚拟 DOM 不是真实的 DOM 元素,而是 js 对象,其中包含了渲染成 DOM 元素信息;
console.log(3);
console.log(this.msg);
console.log(this.$el);
},
mounted() {
// 把 Vue 的虚拟DOM挂载到真实的 DOM 上;
// 如果要在 Vue 中获取 DOM 元素对象,一般在这个钩子中获取
// 项目中的 ajax 请求一般会在这里或者 created 里发送;
console.log(4);
console.log(this.msg);
console.log(this.$el);
},
// 只有当数据发生变化时,才会触发这个函数;
beforeUpdate() {
console.log(5)
},
updated() {
// 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
console.log(6);
},
beforeDestroy() {
// 在 Vue 的实例被销毁之前调用,如果页面中有定时器,我们会在这个钩子中清除定时器;
console.log(7);
},
destroyed() {
// Vue 实例销毁后调用,实例中的属性也不再是响应式的,watch 被移除
console.log(8);
}
});
vm.$set(vm, 'msg', 'hello world'); // 因为 Vue 的数据都是响应式的,只有修改数据才会触发 beforeUpdate 和 updated 钩子
vm.$mount('#app'); // 当创建实例时不传递 el 属性,可以手动挂载到 DOM 节点;
vm.$destroy(); // 手动销毁实例;
</script>
</body>
</html>
二、refs 和 DOM 操作
- Vue 是数据驱动的,不提倡操作 DOM,但是必要的时候还是要操作 DOM,Vue 提供了一个行内属性,专门用来获取 DOM;
- vm.$refs 是 Vue 提供的实例属性,专门用来获取 DOM 元素
2.1 使用 refs
在需要获取的标签添加 ref=”标识符” 行内属性;然后在 Vue 的实例中通过 vm.$refs.标识符 获取这个元素对象,如果有多个相同标识符的 ref,vm.$refs 将会获得一个数组;
2.3 代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p ref="p1">{{msg}}</p>
<ul ref="ulList">
<li v-for="(item, index) in arr" :key="index" ref="wrap">{{item}}</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
// Vue 是数据驱动的,不提倡操作 DOM,但是必要的时候还是要操作 DOM,Vue 提供了一个行内属性,专门用来获取 DOM;
// this.$refs
let vm = new Vue({
el: '#app',
data: {
msg: 'zfpx',
arr: [1, 2, 3, 4]
},
mounted() {
console.log(this.$refs);
// ref 属性可以用来获取 DOM;
// this.$refs.xxx xxx 是你要获取的 DOM 元素对象上 ref 属性的值,例如获取上面的 p 标签,
console.log(this.$refs.p1);
// 如果相同的 ref 值的元素有多个,那么获取到的是一个数组
console.log(this.$refs.wrap);
}
})
</script>
</body>
</html>
三、Vue 异步的 DOM 更新和 nextTick
3.1 Vue 更新 DOM 的机制
Vue 的 DOM 更新不是同步的,而是 异步 的,如果我们希望获取更新数据后渲染出来的 DOM,我们需要使用 nextTick ;
3.2 nextTick
- 语法:
this.$nextTick(callback)
- 把 callback 放到 DOM 更新后执行
3.3 代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p ref="p1">{{msg}}</p>
<ul ref="ulList">
<li v-for="(item, index) in arr" :key="index" ref="wrap">{{item}}</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
// this.$refs
let vm = new Vue({
el: '#app',
data: {
msg: 'zfpx',
arr: [1, 2, 3, 4]
},
mounted() {
this.arr.push(5, 6, 7);
console.log(this.$refs.wrap.length); // 4
// 为啥是4?不是7?
// 因为 Vue 的 DOM 更新是异步的,如果我们希望获取更新数据后渲染出来的 DOM,我们需要使用 nextTick
this.$nextTick(() => {
// 这个 $nextTick 方法会在数据更新后,新的 DOM 挂载后执行;
console.log(this.$refs.wrap.length); // 7
})
}
})
</script>
</body>
</html>
四、template属性
在创建组件或者 Vue 的实例时可以设置一个 template 属性,可以指定 HTML 中的模板 id 或者以字符串的形式声明模板;
4.1 以HTML模板形式:
<!--template 标签并不会被渲染出来-->
<template id="tpls">
<div>
<p v-for="(a, i) in arr">{{a}}</p>
</div>
</template>
创建 Vue 实例
设置 template 属性的值为上面 template 标签的 id,即 “#tpls”
let vm = new Vue({
el: '#app',
data: {
msg: 'zfpx',
arr: [1, 2, 3, 4, 5, 6]
},
template: '#tpls'
})
4.2 设置模板字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
</div>
<!--template 标签并不会被渲染出来-->
<template id="tpls">
<div>
<p v-for="(a, i) in arr">{{a}}</p>
</div>
</template>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: 'zfpx',
arr: [1, 2, 3, 4, 5, 6]
},
template: `<div><p v-for="(a, i) in arr">{{a}}</p></div>`
})
</script>
</body>
</html>
4.3 使用 template 属性的注意事项:
- template:
<div><p v-for="(a, i) in arr">{{a}}</p></div>
或者 template: ‘#id’ - template 属性渲染后会把根DOM元素替换掉
- template 标签的只能有一个子元素
五、组件化和全局组件
5.1 什么是组件?
组件:把页面中重复的的功能抽离出来封装成一个单独的组件,在任何需要的地方使用该组件即可;提高代码的可复用程度和可维护性;
每个组件都是一个 Vue 的实例,那么这个组件也有自己的生命周期,并且也有 data、computed、methods、watch这些属性,每个组件都有自己私有的数据;还可以接受来自上层组件传入的数据;
5.2 注册全局组件
Vue.component(componentName, config)
- componentName 可以使用驼峰,也可以使用 component-name
- 但是在 HTML 中引用时只能写 -
- Vue.component 是全局注册组件,在其他各个 Vue 实例中可以直接使用
5.3 使用组件
在组件的模板(template)或者 HTML 中使用 <组件名></组件名>
5.4 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<handsome-man></handsome-man>
</div>
<div id="app2">
<handsome-man></handsome-man>
</div>
<script src="vue.js"></script>
<script>
// 组件:把页面中重复的的功能抽离出来封装成一个单独的组件,在任何需要的地方使用该组件即可;
// 每个组件都是一个 Vue 的实例,那么这个组件也有自己的生命周期,并且也有data、computed、methods、watch这些属性,每个组件都有自己私有的数据;还可以接受来自上层组件传入的数据;
// 注册一个组件:
// 全局组件 Vue.component(componentName, config)
// 1. componentName 可以使用驼峰,也可以使用 component-name
// 2. 但是在HTML中引用时只能写 -
// 3. Vue.component 是全局注册组件,在其他各个 Vue 实例中可以直接使用
Vue.component('handsomeMan', {
data () {
// 注册组件时 data 属性需要用一个函数返回一个对象
return {
msg: 'zfpx'
}
},
template: `<div>{{msg}}</div>`
});
let vm1 = new Vue({
el: '#app',
data: {}
});
let vm2 = new Vue({
el: '#app2',
data: {}
});
</script>
</body>
</html>
六、局部组件
6.1 局部组件
局部组件是只能在当前 Vue 实例中使用的组件;
6.2 使用局部组件的步骤
- 创建组件
- 注册组件
- 使用组件
6.3 代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<mabin></mabin>
<cxk></cxk>
</div>
<script src="vue.js"></script>
<script type="module">
// 局部组件使用三部曲
// 1. 创建组件
// 2. 注册组件
// 3. 使用组件
// import mabin from './mabin.js';
// import cxk from './cxk.js';
let cxk = {
data() {
return {
content: [
'sing',
'dance',
'rap',
'basketball'
]
}
},
template: `<div>CXK: <span v-for="(item, index) in content" :key="index">{{item}};</span></div>`
};
let mabin = {
// 每一个组件都是一个 Vue 的实例,那么每个组件都有自己的生命周期、computed...
data () {
return {
name: 'mabin',
arr: []
}
},
methods: {
fn() {
this.name = '马宾';
}
},
template: "<div @click='fn'>{{name}}</div>"
};
let vm = new Vue({
el: '#app',
data() {
return {
x: 1,
y: 2
}
},
components: {
// 注册局部组件,像 mabin, cxk 这种被注册的称为子组件,而组件的注册时在的实例称为父组件
mabin,
cxk
}
})
</script>
</body>
</html>
七、组件嵌套
7.1 为什么会嵌套?
父组件使用了子组件,而子组件又使用了孙子组件;即A功能依赖B组件,B组件依赖C组件;
创建一个组件 => 在对应的父组件中进行注册 => 在父组件的标签中直接嵌套子组件的标签名;
7.2 代码示例
grandson 组件
export default {
data() {
return {
gen: 'Grandson'
}
},
template: `<div>{{gen}}</div>`
}
son 组件
import grandson from './grandson.js';
export default {
data() {
return {
gen: 'Son'
}
},
components: {
grandson
},
template: `<div>{{gen}} <grandson></grandson> </div>`
}
最终父组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div>{{gen}}</div>
<son></son>
</div>
<script src="vue.js"></script>
<script type="module">
import son from './son.js';
let vm = new Vue({
el: '#app',
data() {
return {
gen: 'Parent'
}
},
components: {
son
}
});
// 创建一个组件 => 在对应的父组件中进行注册 => 在父组件的标签中直接嵌套子组件的标签名;
</script>
</body>
</html>
八、组件的数据传递(父传子)
8.1 为什么传递数据
- 子组件中的数据不能全是写死的,而是有一部分从父组件传递过来的
- 为了把父组件的数据传递给子组件,子组件在标签上动态绑定一个属性,这个属性绑定父组件的数据,并且在子组件的 props 中注册这个属性
- 子组件如果想使用父组件的数据,就使用对应的 props 就可以(父传子用 props)
8.2 单向数据流
单向数据流:数据只能通过父组件传递给子组件,而不能直接从子组件传给父组件,子组件也不能直接修改父组件的数据;当父组件的数据发生改变之后,子组件的收到的数据也会跟着变化;如上面的例子,直接修改从父组件中的数据会引发 Vue 的报错;
如果子组件想修改父组件的数据,只能通知父组件,让父组件修改数据;
8.3 示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<son :msg="pmsg"></son>
</div>
<script src="vue.js"></script>
<script>
let son = {
data() {
return {
hello: 'xxxx'
}
},
props: ['msg', 'changePMSG'],
template: '<span>{{msg}} <button @click="fn">dddd</button></span>',
methods: {
fn() {
this.msg = 1233; // props 中的数据也会代理到子组件的实例身上,可以直接通过 this 访问
}
}
};
let vm = new Vue({
el: '#app',
data: {
pmsg: 'msg from parent'
},
methods: {
changeMsg() {
this.pmsg = 123445
}
},
components: {
son
}
});
// 1. 子组件中的数据不能全是写死的,而是有一部分从父组件传递过来的
// 2. 为了把父组件的数据传递给子组件,子组件在标签上动态绑定一个属性,这个属性绑定父组件的数据,并且在子组件的props中注册这个属性
// 3. 子组件如果想使用父组件的数据,就使用对应的 props 就可以(父传子用props)
// 单向数据流:数据只能通过父组件传递给子组件,而不能直接从子组件传给父组件,子组件也不能直接修改父组件的数据;当父组件的数据发生改变之后,子组件的收到的数据也会跟着变化;如上面的例子,直接修改从父组件中的数据会引发Vue的报错;
// 如果子组件想修改父组件的数据,只能通知父组件,让父组件修改数据;
</script>
</body>
</html>
九、组件数据传递(子传父)
因为单向数据流的原因,子组件不能直接修改父组件的数据;
9.1 子传父的机制
- 子组件传递给父组件通过事件机制,通知父组件,让父组件修改数据;
- 父组件中使用子组件时要监听一个自定义的事件,如上面的 @change-msg=modify 【事件名不要写驼峰】
- 当子组件要修改某个数据时调用 this.$emit(事件名, 数据);
- 子组件 $emit 后,父组件收到这个事件会执事件绑定的的方法,方法的形参可以接收子组件 $emit 的数据
- 父组件监听事件,给事件绑定一个方法,这个方法有一个形参用于接收子组件 $emit 的数据
- 子组件 emit 事件,并且传入数据
9.3 示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<son :pmsg="msg" @change-msg="modify"></son>
</div>
<script src="vue.js"></script>
<script>
let son = {
data() {
return {
msg: '123'
}
},
template: `<div>{{pmsg}} <button @click="fn">修改</button></div>`,
props: ['pmsg'],
methods: {
fn() {
this.$emit('change-msg', '12345上山打老虎');
}
}
};
let vm = new Vue({
el: '#app',
data: {
msg: 'msg from parent'
},
methods: {
modify(val) {
// console.log(val);
this.msg = val;
}
},
components: {
son
}
});
// 子组件传递给父组件通过事件机制,通知父组件,让父组件修改数据;
// 父组件中使用子组件时要监听一个自定义的事件,如上面的 @change-msg=modify 【事件名不要写驼峰】
// 当子组件要修改某个数据时调用 this.$emit(事件名, 数据);
// 子组件 $emit 后,父组件收到这个事件会执事件绑定的的方法,方法的形参可以接收子组件 $emit 的数据
// 父组件监听事件,给事件绑定一个方法,这个方法有一个形参用于接收子组件 $emit 的数据
// 子组件 emit 事件,并且传入数据
</script>
</body>
</html>
十、props验证
props 属性除了设置为一个数组还可以设置一个对象,其中 key 是 props 名,值是一个对象,在这个对象中可以设置默认值以及对这个 props 的校验规则;
10.1 常见的校验:
- type: Number, // 类型校验,要求 pmsg 必须是某种类型,如果类型不对会抛出警告
- required: true, // 必传校验,如果不传会引发警告
- default: 250, // 设置默认值,如果不传的时候使用默认值
- validator(val) { // 验证器函数,有问题可以抛出异常,没有问题要 return true
10.2 代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<child :msg="pmsg"></child>
</div>
<script src="vue.js"></script>
<script>
let child = {
data () {
return {
xxx: 111
}
},
template: `<p>{{msg}}</p>`,
props: {
msg: {
type: Number, // 类型校验,要求 pmsg 必须是某种类型,如果类型不对会抛出警告
required: true, // 必传校验,如果不传会引发警告
default: 250, // 设置默认值,如果不传的时候使用默认值
validator (val) { // 验证器
console.log(val);
if (val > 250) {
throw new Error('太大了')
} else {
return true
}
}
}
}
};
let vm = new Vue({
el: '#app',
data: {
pmsg: 200
},
components: {
child
}
})
</script>
</body>
</html>