写在前面

因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。

文章的原地址:github.com/answershuto…

在学习过程中,为Vue加上了中文的注释github.com/answershuto…,希望可以对其他想学习Vue源码的小伙伴有所帮助。

可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。

什么是Vue组件?

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。




Vue组件间通信

父组件向子组件通信

方法一:props

使用props,父组件可以使用props向子组件传递数据。

父组件vue模板father.vue

<template>
    <child :msg="message"></child>
</template>

<script>

import child from './child.vue';

export default {
    components: {
        child
    },
    data () {
        return {
            message: 'father message';
        }
    }
}
</script>

子组件vue模板child.vue

<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    }
}
</script>


方法二 使用$children

使用$children可以在父组件中访问子组件。




子组件向父组件通信


方法一:使用vue事件

父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件。

父组件vue模板father.vue

<template>
    <child @msgFunc="func"></child>
</template>

<script>

import child from './child.vue';

export default {
    components: {
        child
    },
    methods: {
        func (msg) {
            console.log(msg);
        }
    }
}
</script>

子组件vue模板child.vue

<template>
    <button @click="handleClick">点我</button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {
        handleClick () {
            //........
            this.$emit('msgFunc');
        }
    }
}
</script>


方法二: 通过修改父组件传递的props来修改父组件数据

这种方法只能在父组件传递一个引用变量时可以使用,字面变量无法达到相应效果。因为饮用变量最终无论是父组件中的数据还是子组件得到的props中的数据都是指向同一块内存地址,所以修改了子组件中props的数据即修改了父组件的数据。

但是并不推荐这么做,并不建议直接修改props的值,如果数据是用于显示修改的,在实际开发中我经常会将其放入data中,在需要回传给父组件的时候再用事件回传数据。这样做保持了组件独立以及解耦,不会因为使用同一份数据而导致数据流异常混乱,只通过特定的接口传递数据来达到修改数据的目的,而内部数据状态由专门的data负责管理。


方法三:使用$parent

使用$parent可以访问父组件的数据。




非父子组件、兄弟组件之间的数据传递

非父子组件通信,Vue官方推荐使用一个Vue实例作为中央事件总线

Vue内部有一个事件机制,可以参考源码

$on方法用来监听一个事件。

$emit用来触发一个事件。

/*新建一个Vue实例作为中央事件总嫌*/
let event = new Vue();

/*监听事件*/
event.$on('eventName', (val) => {
    //......do something
});

/*触发事件*/
event.$emit('eventName', 'this is a message.');




多层级父子组件通信:

在Vue1.0中实现了$broadcast与$dispatch两个方法用来向子组件(或父组件)广播(或派发),当子组件(或父组件)上监听了事件并返回true的时候会向爷孙级组件继续广播(或派发)事件。但是这个方法在Vue2.0里面已经被移除了。

之前在学习饿了么的开源组件库element的时候发现他们重新实现了broadcast以及dispatch的方法,以mixin的方式引入,具体可以参考《说说element组件库broadcast与dispatch》。但是跟Vue1.0的两个方法实现有略微的不同。这两个方法实现了向子孙组件事件广播以及向多层级父组件事件派发的功能。但是并非广义上的事件广播,它需要指定一个commentName进行向指定组件名组件定向广播(派发)事件。

其实这两个方法内部实现还是用到的还是$parent以及$children,用以遍历子节点或是逐级向上查询父节点,访问到指定组件名的时候,调用$emit触发指定事件。



Vue组件通信实践记录


Vue2.4组件间通信新姿势


Clock.vue

 <template>
  <div class="clock" :class="className">
    {{setTime(h)}}:
    {{setTime(m)}}:
    {{setTime(s)}}
  </div>
</template>

<script>
export default {
  name: 'clock',
  data () {
    return {
      h: 0,
      m: 0,
      s: 0
    }
  },
  props:['className'],
  mounted(){
    this.clock();
    setInterval(()=>{
      this.clock();
    },1000);
  },
  methods:{
   setTime(num){
      return num < 10 ? ("0" + num) : num;
    },
    clock(){
      var oDate = new Date();
      this.h = oDate.getHours();
      this.m = oDate.getMinutes();
      this.s = oDate.getSeconds();
    }
  }
}
</script>

<style scoped>
.clock{
  display: inline-block;
  font-size: 14px;
}
</style>

 index.js

 import Clock from './Clock.vue';

export default {
    install(Vue){
        Vue.component('Clock',Clock);
    }
};



main.js

import Vue from 'vue';

import Clock from './components/Clock';

Vue.use(Clock);    // Clock 就是全局组件








Vue 非父子组件通信

  组件是Vue核心的一块内容,组件之间的通信也是很基本的开发需求。组件通信又包括父组件向子组件传数据,子组件向父组件传数据,非父子组件间的通信。前两种通信Vue的文档都说的很清楚,但是第三种文档上确只有下面的几句

具体如何去实现却没有很详细的说明,于是自己试着进行了实现。先看下简单的通信效果:

就是点击了一个组件,另一个组件的数字递加。

html如下:

<div id="app">
   <component-a></component-a>
   <component-b></component-b>
</div>

再来看一下如何实现每一个组件:

  var bus = new Vue() //首先建立一个空的Vue实例作为事件的中转

    Vue.component('component-a',{
        template: `<div><button @click="incrementB">{{masgA}}</button></div>`, //添加点击事件incrementB ,因为点击A需要增加B
        data ()    {
            return {
                masgA : 0
            }
        },
        methods: {
            incrementB: function () { //增加B的事件
                bus.$emit('incrementB') //中转站bus 触发incrementB事件
            }
        },
        mounted: function () {
            var _this = this
            bus.$on('incrementA',function(){ //中转站bus自定义increamentA事件用来增加msgA,这个事件最终由组件B进行触发
                _this.masgA ++
            })
       //bus.$on('incrementA',()=>{ //这里也可以用箭头函数,就不会有_this这个变量了,因为箭头函数不会改变this指向
       // this.masgA ++
       //}) } })

从上面的代码可以看出真正的改变方法是通过bus里注册监听事件来实现的,同理代component-b也是一样

  Vue.component('component-b',{
        template: `<div><button @click="incrementA">{{masgB}}</button></div>`,
        data ()    {
            return {
                masgB : 0
            }
        },
        methods: {
            incrementA: function () {
                bus.$emit('incrementA')
            }
        },
        mounted: function(){
            bus.$on('incrementB',() => {
                this.masgB ++
            })
        }
    })

完整代码如下,可直接复制运行

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>非父子组件通信</title>
 6 </head>
 7 <body>
 8     <div id="app">
 9         <component-a></component-a>
10         <component-b></component-b>
11     </div>
12     <script src=""></script>
13 </body>
14 <script>
15     var bus = new Vue() //首先建立一个空的Vue实例作为事件的中转
16 
17     Vue.component('component-a',{
18         template: `<div><button @click="incrementB">{{masgA}}</button></div>`, //添加点击事件
19         data ()    {
20             return {
21                 masgA : 0
22             }
23         },
24         methods: {
25             incrementB: function () {
26                 bus.$emit('incrementB')
27             }
28         },
29         mounted: function () {
30             var _this = this
31             bus.$on('incrementA',function(){
32                 _this.masgA ++
33             })
34             bus.$on('incrementA',()=>{
35                 this.masgA ++
36             })
37         }
38     })
39 
40     Vue.component('component-b',{
41         template: `<div><button @click="incrementA">{{masgB}}</button></div>`,
42         data ()    {
43             return {
44                 masgB : 0
45             }
46         },
47         methods: {
48             incrementA: function () {
49                 bus.$emit('incrementA')
50             }
51         },
52         mounted: function(){
53             bus.$on('incrementB',() => {
54                 this.masgB ++
55             })
56         }
57     })
58 
59     var vm = new Vue({
60         el: "#app"
61     })
62 </script>

  同时也可以看出这么做仅有两个组件就有些麻烦,事件的流向不是很清晰,所以在出现复杂的场景时需要使用VueX进行管理。

  本文结束,有任何不同的意见欢迎在留言区讨论。



短信倒计时

<template>
    <div>
    	<img src=""/>
        <button @click='demo'>到五的时候取消监听</button>
        <p>{{ziksang1}}</p>
        <button @click='demo2'>只监听一次,传一个参过来</button>
        <p>{{ziksang2}}</p>
        <hr />
        <input type="" name="" v-model='tel' />
        <button :style="styleObject" :disabled="flag"  @click='btnClick' v-text="btnTime"></button>
        <button @click='btnSbm'>下一步</button>  
    </div>
</template>
<script>
    export default {
        created () {
            this.$on('demo',()=>{
                this.ziksang1++
                if(this.ziksang1 == 5){
                    this.$off('demo')
                }
            }),
            this.$once('demo2',(value)=>{
                this.ziksang2+=value
            })
        },
        data () {
            return {
            	styleObject:{
			            color:'red',
			            fontSize:'30px',
			            display:'block',
			            margin:'0 auto'
			        },
                ziksang1 : 0,
                ziksang2 : 0,
                flag:false,
                btnTime:"获取验证啦",
                tel:''
            }
        },
        methods : {
        	checkTel(val){
        		if(!(/^1[3|4|5|8][0-9]\d{4,8}$/.test(val))){
        			this.$toast('电话号不合格!');
        		 	return false;
        		}
        	},
            demo () {
                this.$emit('demo')
            },
            demo2 () {
                this.$emit('demo2',10)
            },
            btnClick(){
            	this.checkTel(this.tel);
            	this.flag=true;
            	this.btnTime=60;
            	let timer=setInterval(()=>{
            		this.btnTime--;
            		if(this.btnTime == 0){
            			this.btnTime="重新获取"
            			clearInterval(timer);
            			this.flag=false;
            		}
            	},100)
            },
            btnSbm(){
            	console.log(this.$route.params.name)
            	this.$toast(this.tel);
            	this.$store.dispatch('AskTost',this.tel);
            }
        }
    }
</script>
<style scoped>
	img{
		display: block;
		margin: 10px auto;
		width: 200px;
		border: 1px solid red;
	}
</style>

Vue组件通信实践记录


Vue2.4组件间通信新姿势


Clock.vue

 <template>
  <div class="clock" :class="className">
    {{setTime(h)}}:
    {{setTime(m)}}:
    {{setTime(s)}}
  </div>
</template>

<script>
export default {
  name: 'clock',
  data () {
    return {
      h: 0,
      m: 0,
      s: 0
    }
  },
  props:['className'],
  mounted(){
    this.clock();
    setInterval(()=>{
      this.clock();
    },1000);
  },
  methods:{
   setTime(num){
      return num < 10 ? ("0" + num) : num;
    },
    clock(){
      var oDate = new Date();
      this.h = oDate.getHours();
      this.m = oDate.getMinutes();
      this.s = oDate.getSeconds();
    }
  }
}
</script>

<style scoped>
.clock{
  display: inline-block;
  font-size: 14px;
}
</style>

 index.js

 import Clock from './Clock.vue';

export default {
    install(Vue){
        Vue.component('Clock',Clock);
    }
};



main.js

import Vue from 'vue';

import Clock from './components/Clock';

Vue.use(Clock);    // Clock 就是全局组件








Vue 非父子组件通信

  组件是Vue核心的一块内容,组件之间的通信也是很基本的开发需求。组件通信又包括父组件向子组件传数据,子组件向父组件传数据,非父子组件间的通信。前两种通信Vue的文档都说的很清楚,但是第三种文档上确只有下面的几句

具体如何去实现却没有很详细的说明,于是自己试着进行了实现。先看下简单的通信效果:

就是点击了一个组件,另一个组件的数字递加。

html如下:

<div id="app">
   <component-a></component-a>
   <component-b></component-b>
</div>

再来看一下如何实现每一个组件:

  var bus = new Vue() //首先建立一个空的Vue实例作为事件的中转

    Vue.component('component-a',{
        template: `<div><button @click="incrementB">{{masgA}}</button></div>`, //添加点击事件incrementB ,因为点击A需要增加B
        data ()    {
            return {
                masgA : 0
            }
        },
        methods: {
            incrementB: function () { //增加B的事件
                bus.$emit('incrementB') //中转站bus 触发incrementB事件
            }
        },
        mounted: function () {
            var _this = this
            bus.$on('incrementA',function(){ //中转站bus自定义increamentA事件用来增加msgA,这个事件最终由组件B进行触发
                _this.masgA ++
            })
       //bus.$on('incrementA',()=>{ //这里也可以用箭头函数,就不会有_this这个变量了,因为箭头函数不会改变this指向
       // this.masgA ++
       //}) } })

从上面的代码可以看出真正的改变方法是通过bus里注册监听事件来实现的,同理代component-b也是一样

  Vue.component('component-b',{
        template: `<div><button @click="incrementA">{{masgB}}</button></div>`,
        data ()    {
            return {
                masgB : 0
            }
        },
        methods: {
            incrementA: function () {
                bus.$emit('incrementA')
            }
        },
        mounted: function(){
            bus.$on('incrementB',() => {
                this.masgB ++
            })
        }
    })

完整代码如下,可直接复制运行

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>非父子组件通信</title>
 6 </head>
 7 <body>
 8     <div id="app">
 9         <component-a></component-a>
10         <component-b></component-b>
11     </div>
12     <script src=""></script>
13 </body>
14 <script>
15     var bus = new Vue() //首先建立一个空的Vue实例作为事件的中转
16 
17     Vue.component('component-a',{
18         template: `<div><button @click="incrementB">{{masgA}}</button></div>`, //添加点击事件
19         data ()    {
20             return {
21                 masgA : 0
22             }
23         },
24         methods: {
25             incrementB: function () {
26                 bus.$emit('incrementB')
27             }
28         },
29         mounted: function () {
30             var _this = this
31             bus.$on('incrementA',function(){
32                 _this.masgA ++
33             })
34             bus.$on('incrementA',()=>{
35                 this.masgA ++
36             })
37         }
38     })
39 
40     Vue.component('component-b',{
41         template: `<div><button @click="incrementA">{{masgB}}</button></div>`,
42         data ()    {
43             return {
44                 masgB : 0
45             }
46         },
47         methods: {
48             incrementA: function () {
49                 bus.$emit('incrementA')
50             }
51         },
52         mounted: function(){
53             bus.$on('incrementB',() => {
54                 this.masgB ++
55             })
56         }
57     })
58 
59     var vm = new Vue({
60         el: "#app"
61     })
62 </script>

  同时也可以看出这么做仅有两个组件就有些麻烦,事件的流向不是很清晰,所以在出现复杂的场景时需要使用VueX进行管理。

  本文结束,有任何不同的意见欢迎在留言区讨论。



短信倒计时

<template>
    <div>
    	<img src=""/>
        <button @click='demo'>到五的时候取消监听</button>
        <p>{{ziksang1}}</p>
        <button @click='demo2'>只监听一次,传一个参过来</button>
        <p>{{ziksang2}}</p>
        <hr />
        <input type="" name="" v-model='tel' />
        <button :style="styleObject" :disabled="flag"  @click='btnClick' v-text="btnTime"></button>
        <button @click='btnSbm'>下一步</button>  
    </div>
</template>
<script>
    export default {
        created () {
            this.$on('demo',()=>{
                this.ziksang1++
                if(this.ziksang1 == 5){
                    this.$off('demo')
                }
            }),
            this.$once('demo2',(value)=>{
                this.ziksang2+=value
            })
        },
        data () {
            return {
            	styleObject:{
			            color:'red',
			            fontSize:'30px',
			            display:'block',
			            margin:'0 auto'
			        },
                ziksang1 : 0,
                ziksang2 : 0,
                flag:false,
                btnTime:"获取验证啦",
                tel:''
            }
        },
        methods : {
        	checkTel(val){
        		if(!(/^1[3|4|5|8][0-9]\d{4,8}$/.test(val))){
        			this.$toast('电话号不合格!');
        		 	return false;
        		}
        	},
            demo () {
                this.$emit('demo')
            },
            demo2 () {
                this.$emit('demo2',10)
            },
            btnClick(){
            	this.checkTel(this.tel);
            	this.flag=true;
            	this.btnTime=60;
            	let timer=setInterval(()=>{
            		this.btnTime--;
            		if(this.btnTime == 0){
            			this.btnTime="重新获取"
            			clearInterval(timer);
            			this.flag=false;
            		}
            	},100)
            },
            btnSbm(){
            	console.log(this.$route.params.name)
            	this.$toast(this.tel);
            	this.$store.dispatch('AskTost',this.tel);
            }
        }
    }
</script>
<style scoped>
	img{
		display: block;
		margin: 10px auto;
		width: 200px;
		border: 1px solid red;
	}
</style>
Logo

前往低代码交流专区

更多推荐