一、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.$refslet 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.$refslet 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,我们需要使用 nextTickthis.$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>
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
