vue组件通信方式汇总

937 阅读1分钟

前提回顾

总结vue2.x组件通信的方法 为了能让需要通信的两个组件可以说上话,往往需要借助一些手段来实现这个目的,本文的写作目标就是为了总结每一种可以用于通信手段的方法。

第一:props/$emit

**props/emit适用于父子级组件传递消息下面使用了props/emit适用于父子级组件传递消息** 下面使用了props/emit方法做到了让Foo组件和Bar组件通信

  • App.vue
<template>
  <div id="app">
    <Foo></Foo>
  </div>
</template>

<script>
import Foo from "./components/Foo";
export default {
  name: "App",
  components: {
    Foo,
  },
};
</script>
  • components/Foo.vue
<template>
<div id='Foo'>
    使用props/$emit来做到父子组件通信
    <Bar 
    	:title="title"
    	@changeTitle='handleChangeTitle'>
    </Bar>
</div>
</template>

<script>
import Bar from './Bar';
export default {
    name: 'Foo' ,
    components: {
      Bar
    },
    data() {
        return {
          title:"this is a message"
        }
    },
    methods: {
      handleChangeTitle(props){
        this.title=props
      }
    }
};
</script>
  • components/Bar.vue
<template>
<div id='Bar'>
  使用props/$emit来做到父子组件通信
  {{title}}
  <button @click="toParent">改变title</button>
</div>
</template>

<script>
export default {
    name: 'Bar' ,
    props: ['title'],
    methods: {
      toParent(){
        this.$emit('changeTitle','this is a new message')
      }
    }
};
</script>

第二:$refs/ref

$refs/ref也可以用于父子组件通信,其中ref是给元素或子组件注册引用信息,$refs是获取通过 ref 注册的引用

  • App.vue的代码同上省略
  • components/Foo.vue
<template>
<div id='Foo'>
    Foo
    <Bar 
      :title="title" 
      @changeTitle='handleChangeTitle'
      ref='bar'
      ></Bar>
      <button
        @click="getBar"
      >getBar</button>
</div>
</template>

<script>
import Bar from './Bar';
export default {
    name: 'Foo' ,
    components: {
      Bar
    },
    props: [''],
    data() {
        return {
          title:"this is a message"
        }
    }
    methods: {
      getBar(){
        // 调用了Bar组件上的setBarTitle方法
        console.log(this.$refs.bar.setBarTitle());
        // console.log(this.$refs.bar);
      }
    }
};
</script>

  • components/Bar.vue
<template>
<div id='Bar'>
  Bar
  {{title}}
  <button @click="toParent">改变title</button>
</div>
</template>

<script>
export default {
    name: 'Bar' ,
    props: ['title'],
    methods: {
      toParent(){
        this.$emit('changeTitle','this is a new message')
      },
      setBarTitle(){
        console.log('changeBarTitle');
      }
    }
};
</script>

第三:children/parent

$children/$parent也可以用于父子组件通信,其中$parent是获取当前组件的父组件实例,$children是获取当前组件的子组件实例 使用$children获取组件的子组件实例的时候是依赖下标来获取的,这种获取方式是不稳定的,易出错。

  • App.vue的代码同上省略
  • components/Foo.vue
<template>
<div id='Foo'>
    Foo
      <button
        @click="getChildren"
      >getChildren</button>
</div>
</template>

<script>
import Bar from './Bar';
export default {
    name: 'Foo' ,
    components: {
      Bar
    },
    props: ['']
    methods: {
      getChildren(){
        //可以获取到当前Foo组件的所有子组件实例
        //(是数组格式[VueCompoent])
        //依赖下标来获取具体子组件实例
        //即通过this.$children[下标]
        console.log(this.$children);
      }
    }
};
</script>
<style>
</style>
  • components/Bar.vue
<template>
<div id='Bar'>

  <button
    @click="getParent">
  getBarParent</button>
  
</div>
</template>

<script>
export default {
    name: 'Bar' ,
    methods: {
      getParent(){
        //就可以获取到Bar组件的父级组件了
        //接着可以调用父级组件上的方法等等操作进行通信
        console.log(this.$parent);
      }
    }
};
</script>

第四:attrs/listeners

$attrs/$listeners可以用来处理多层级父子组件通信。$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外),当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器,它可以通过v-on="$listeners"传入内部组件

  • App.vue
<template>
  <div id="app">
    <Foo></Foo>
  </div>
</template>

<script>
import Foo from './components/Foo';
export default {
  name: "App",
  components: {
    Foo
  },
};
</script>
  • components/Foo.vue
<template>
  <div>
    这里是Foo
    <Bar 
      title="this is a foo" 
      class="heihei" 
      @a="handleA" 
      @b="handleB"
      @click='handleFooClick'
      disabled='disabled'
    ></Bar>
  </div>
</template>

<script>
import Bar from "./Bar";
export default {
  components: {
    Bar,
  },

  methods: {
    getName() {
      return "CompA";
    },
    handleA() {
      console.log("handleA")
    },
    handleB() {
      console.log("handleB")
    },
    handleFooClick(){
      console.log(" handleFooClick")
    }
  },
};
</script>
  • components/Bar.vue
<template>
  <div>
    这里是Bar
    <Baz 
      :title="$attrs.title"
      v-on="$listeners"
      ></Baz>

    <button 
      disabled='$attrs.disabled'
    >禁用状态</button>
  </div>
</template>

<script>
import Baz from "./Baz";
export default {
  //一般可以用props来接收父级组件传递下来的内容
  // 如果传入的值没有在 props 中声明,那么就会在 $attrs 获取到
  // props:["title"],


  //默认行为:
  //我们之前书写了<Bar title="this is a foo" ></Bar>
  //并且没有通过props来接收声明title的话Vue会把title="this is a foo"
  //给添加到组件容器内即<div title="this is a foo"></div>
  //如果不希望出现这种情况可以使用inheritAttrs: false
  inheritAttrs: false,
  
  
  components: {
    Baz,
  },
  mounted() {
    // $attrs 属性:
    // 如果传入的值没有在 props 中声明,那么就会在 $attrs 获取到

    //{title: "this is a foo"}
    console.log(this.$attrs,'this.$attrs');
    //{a: ƒ, b: ƒ, click: ƒ}
    console.log(this.$listeners,'this is bar');
    
    this.$listeners.a()
  },

  methods: {
    getName() {
      return "CompB";
    },
  },
};
</script>
  • components/Baz.vue
<template>
  <div>
    这里是Baz
    {{ title }}
  </div>
</template>

<script>
export default {
  props: ["title"],
  methods: {
      
  },
  mounted(){
    //成功传递进来了{a: ƒ, b: ƒ, click: ƒ}
    console.log(this.$listeners,'this is baz');
  }
};
</script>

<style></style>

第五:provide/inject

provide/inject可以用来处理多层级父子组件通信,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

  • App.vue
<template>
  <div id="app">
    <CompA></CompA>
  </div>
</template>

<script>
import CompA from "./components/CompA";
export default {
  name: "App",
  components: {
    CompA
  },
};
</script>
  • components/CompA.vue
<template>
  <div>
    这里是CompA
    <CompB></CompB>
  </div>
</template>

<script>
import CompB from "./CompB";
export default {
  components: {
    CompB,
  },
  mounted() {
  },


  //把当前的组件实例传递给别人
  //别人拿到组件实例后可以调用该组件实例上的方法
  //第二种写法:
  provide() {
    return {
      foo: "foo",
      compA: this,  //注意this的指向问题
    };
  },

  // 第一种写法:
  // provide: {
  //   foo: "foo",
  //   compA: this, //注意this的指向问题
  // },

  methods: {
    getName() {
      return "CompA";
    },
  },
};
</script>
  • components/CompB.vue
<template>
  <div>
    这里是CompB
    <CompC></CompC>
  </div>
</template>

<script>
import CompC from "./CompC";
export default {
  components: {
    CompC,
  },
  provide() {
    return {
      compA: this, //this指向compB组件
                   //名字却故意取为compA
                   //在注入时会取最近的注入名
    };
  },
  methods: {
    getName() {
      return "CompB";
    },
  },
};
</script>
  • components/CompC.vue
<template>
  <div>
    CompC

    直接就可以使用了:{{foo}}

    <button @click="getCompA">getCompA</button>
  </div>
</template>

<script>
export default {
  inject: ["foo", "compA"],
  methods: {
    getCompA(){
      //获取到组件实例
      console.log(this.compA);
      //该组件实例上有getName方法
      //输出的结果为CompB(取的是最近的注入名 "compA" )
      console.log(this.compA.getName())
    }
  },
};
</script>

第六:EventBus通信

EventBus其实可以说是综合运用了$on$off$emit$once,也可以用来解决组件通信,但是太灵活了也不是什么好事,一定要保证先监听再发送

  • bus.js
import Vue from "vue";

// event bus
export const bus = new Vue();
  • components/CompA.vue
<template>
  <div>
    CompA
    <CompB></CompB>
  </div>
</template>

<script>
import CompB from "./CompB";
import {bus} from '../bus';
export default {
  components: {
    CompB,
  },
  mounted() {
    bus.$on('to-CompoA',(message)=>{
      console.log(message);
    })
  }
};
</script>
  • components/CompB.vue
<template>
  <div>
    CompB
    <CompC></CompC>
  </div>
</template>

<script>
import CompC from "./CompC";
export default {
  components: {
    CompC,
  },
};
</script>
  • components/CompC.vue
<template>
  <div>
    CompC
    <button @click="getCompA">getCompA</button>
  </div>
</template>

<script>
import {bus} from '../bus';
export default {
  methods: {
    getCompA(){
      bus.$emit('to-CompoA','this is message from  CompoC')
    }
  },
};
</script>
  • App.vue
<template>
  <div id="app">
    <CompA></CompA>
  </div>
</template>

<script>
import CompA from './components/CompA';
export default {
  name: "App",
  components: {
     CompA
  },
};
</script>

第七:v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

  • App.vue
<template>
  <div id="app">
    <Foo></Foo>
  </div>
</template>

<script>
import Foo from './components/Foo';
export default {
  name: "App",
  components: {
    Foo
  },
};
</script>
  • components/Foo.vue
<template>
  <div>
    这里是Foo
    <Bar 
        v-model="initValue"
    ></Bar>
  </div>
</template>

<script>
import Bar from "./Bar";
export default {
  components: {
    Bar,
  },
  data(){
    return{
      initValue:'hello init'
    }
  }
};
</script>
  • components/Bar.vue
<template>
  <div>
    这里是Bar
    <input type="text"
      v-bind:value="handle"
      v-on:change="$emit('handleEvent', $event.target.value+'fk')"
    >
  </div>
</template>

<script>
export default {
  
  components: {
  },
  model:{
    prop:'handle',
    event:'handleEvent'
  },
  
  //仍然需要在组件的 props 选项里声明
  // handle 这个 prop。
  props: {
    handle: String
  },
};
</script>

第八:sync

.sync修饰符也可以用于组件通信。在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

    <script>
      const Modal = {
        template: `<div v-if="visible">
          modal
          {{visible}}
          {{number}}
          <button @click="handleClick">x</button>
        </div>`,
        props: ["visible", "number"],
        methods: {
          handleClick() {
            this.$emit("update:visible", false);
            this.$emit("update:number", 10);
          },
        },
      };

      const app = new Vue({
        el: "#app",
        components: {
          Modal,
        },
        data: {
          msg: "hello",
          showModel: false,
          count: 1,
        },
        methods: {
          handleShowModel() {
            this.showModel = true;
          },
          // handleCloseModel() {
          //   this.showModel = false;
          // },
        },
        template: `<div>
        app
        <button @click="handleShowModel"> showModel</button>
        <Modal :visible.sync="showModel" :number.sync="count" ></Modal>
        </div>`,
      });
    </script>

第九:VueX

暂时省略