• 场景:
    某个活动页需要在a页面填写表单,调取接口,接口返回结果如果成功则显示在b页面,如果失败则在a页面提示。
  • 方案:使用vue单页面应用,分为entrance组件和room组件
  • 思路:调取接口这一步需要在a组件完成,如果失败才能够直接在a(entrance组件)提示,如果成功需要把数据传给b(room组件)。

    以上,进入正题,vue组件间的通信要怎么做,给出思路:

    一.子1传父,父传子2

    这是最原始的做法。
    img
    (网图)以上基本就可以概括。

  • 父组件向子组件通信
    这是很常用的一种情况。官网有写:链接

    通过Props向子组件传递数据…

    Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。

    prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态,所以不应该在子组件内部改变prop,每次父组件更新时,子组件的所有 prop 都会更新为最新值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <div id="app">
    父组件:
    <input v-model="msg">
    <hr>
    子组件:
    <child :message="msg"></child>
    </div>

    <script>
    var vm = new Vue({
    el:'#app',
    data:{
    msg:'Hello World'
    },
    components:{
    'child':{
    props:['message'],
    template:'<input v-model="message">'
    }
    }
    })
    </script>

    如果需要改变prop:

    • prop作为初始值传入,子组件需要将它作为本地数值的初始值使用
      定义一个局部data属性,并将prop的初始值作为局部数据的初始值

      1
      2
      3
      4
      5
      6
      props:[data1],
      data:function(){
      return {
      counter:this.data1
      }
      }
    • prop作为需要被转变的原始值传入

      定义一个computed属性

      1
      2
      3
      4
      5
      6
      props:[data2],
      computed:{
      computedSize:function(){
      return this.data2.trim().toLowerCase();
      }
      }

    总结

    子组件想要获取父组件的data

    父组件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <template>
    <child :msg="message"></child>
    </template>

    <script>

    import child from './child.vue';

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

    子组件:child.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <div>{{msg}}</div>
    </template>

    <script>
    export default {
    props: {
    msg: {
    type: String,
    required: true
    }
    }
    }
    </script>
  • 子组件向父组件通信

    官网链接

    通过事件向父级组件发送消息

    每个vue实例都实现了事件接口:

    1. 使用$on监听事件
    2. 使用$emit触发事件,回调给父组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    <div id="app">
    <parent></parent>
    </div>

    <!--父组件-->
    <template id="parent-template">
    <div>
    <span>父组件的数据是:</span> ---> `parentMsg`
    <child @get-message="get"></child>
    </div>
    </template>
    <script>
    //注册父组件
    var Parent = Vue.component('parent',{
    template: '#parent-template',
    data(){
    return {parentMsg:'111'}
    },
    //接收子组件传过来的数据
    methods:{
    get:function(msg){
    alert(msg);
    this.parentMsg = msg;
    }
    }
    });
    <script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <!--子组件-->

    <template id="child-template">
    <div>
    <span>子组件的数据是:</span> ---> childMsg
    <button @click="send">发送</button>
    </div>
    </template>
    <script>
    //注册子组件

    var Child = Vue.component('child',{
    template:'#child-template',
    data(){
    return {childMsg:'99999'}
    },
    //把子组件数据发送给父组件
    methods:{
    send:function(){
    this.$emit('get-message', this.childMsg)
    }
    }
    });

    var vm = new Vue({
    el:'#app',
    components:{
    'parent': Parent
    }
    });

    </script>

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

    • $on方法监听一个事件
    • $emit方法触发一个事件
    • Vue官方推荐使用一个Vue实例作为中央事件总线 —— Bus
    • 实例:
      在组件中,可以使用$emit$on$off 分别来分发、监听、取消监听事件:
      直接到全局定义bus:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var eventBus = {
    install(Vue,options) {
    Vue.prototype.$bus = vue
    }
    };
    Vue.use(eventBus);


    //分发事件的组件A
    methods: {
    todo: function () {
    this.$bus.$emit('todoSth', params); //params是传递的参数
    //...
    }
    }

    // 监听事件的组件B
    created() {
    this.$bus.$on('todoSth', (params) => { //获取传递的参数并进行操作
    //todo something
    })
    },
    // 最好在组件销毁前 清除事件监听
    beforeDestroy () {
    this.$bus.$off('todoSth');
    },

​ 或者将 事件中间件单独拎出来(需要注意在组件里引用)

​ bus.js:

1
2
var bus = new Vue();
module.exports = bus;

​ 分发事件的组件A:

1
2
3
4
5
6
7
8
9
 import bus from '../common/bus.js'
methods:{
sendData(){
this.data = "123"
}
}
beforeDestroy() {
bus.$emit('pbgdata', this.data);//'pbgdata'为组件的eventName,用于区分监听不同的组件内容
}

​ 接收事件的组件B:

1
2
3
4
5
6
7
8
9
10
11
12
13
import bus from '../common/bus.js'

created() {
bus.$on('pbgdata', (data) => {
console.log(data)//这就是从A组件得到的数据

})


},
beforeDestroy() {
bus.$off('pbgdata');
},

从上面可以看出,我是在beforeDestroy()去分发事件,同时在B组件的created去监听事件,在beforeDes()去销毁,这是为啥?
这里就是遇到的另一个问题:

vue-router中使用EventBus传值的坑

一开始我在A组件得到数据后就使用$emit分发,然后第一次在B的created里并没有拿到(无销毁操作),第二次进入才拿到,且多次进入会打印多行结果。

vue-router切换的时候,会先加载新的组件,当新的组件渲染好但是还没mount的时候,销毁旧组件,然后再挂载新组件,也就是说当B页面的生命周期进行到beforeMount的时候,下一步走到的就是A页面的beforeDestory方法和接下去的destroyed方法
所以我们再B组件的created方法里去使用$on 去监听自定义事件时$emit事件已经发出去了,这个时候监听器还没有注册,所以才需要再A的beforeDestroy或者destroyed去执行$emit。
以下为找的的vue-router切换时相关生命周期(网图):
img

总结下就是这样的步骤:

新组件: beforeCreate
新组件: created
新组件: beforeMount
旧组件: beforeDestory
旧组件: destroy
新组件: mounted

其他:当应用足够复杂情况下,请使用vuex进行数据管理。

参考链接:

vue-router中使用EventBus传值需要注意到的问题

聊聊 Vue.js 组件间通信