你能说出几种,实现Vue组件通信的方式?5种最常见的

234 阅读5分钟

1. props 和$emit

这种方式通常用于父子组件之间的传值,父组件通过自定义属性的方式将值传递给子组件,子组件通过props进行接收。

子组件通过$emit自定义事件的方式向父组件传递值,父组件需要监听该事件来进行接收子组件传来的值。

举个例子:

父组件

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>
      <button @click="changeMsg">更改数据</button>
    </div>
    <div>子组件数据:{{ childData }}</div>
    <child1 :msg="msg" @childData="childData"></child1>
  </div>
</template>
<script>
import child1 from "./child1.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      childData: "",
    };
  },
  components: {
    child1
  },
  methods: {
    changeMsg() {
      this.msg = "变成小猪课堂";
    },
    // 监听子组件事件
    childData(data) {
      this.childData = data;
    },
  },
};
</script>

子组件

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="sendData">传递数据给父组件</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  methods: {
    // 点击按钮,使用$emit向父组件传递数据
    sendData() {
      this.$emit("childData", "我是子组件数据");
    },
  },
};
</script>

2. $parent$children

$parent子组件获取父组件属性和方法

子组件

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="getParentData">使用$parent</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  methods: {
    // 通过$parent方式获取父组件值
    getParentData() {
    // 可以打印出所有父组件的属性和方法
      console.log("父组件", this.$parent);
    },
  },
};
</script>

$children 获取子组件数据

直接获取子组件的相关属性或方法,不仅限于数据。

父组件

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>
      <button @click="changeMsg">更改数据</button>
    </div>
    <div>
      <button @click="getChildByRef">使用$children</button>
    </div>
    <div>子组件数据:{{ childData }}</div>
    <child1 :msg="msg" @childData="getChildData"></child1>
    <child2 :msg="msg"></child2>
  </div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  },
  methods: {
    // 监听子组件的自定义事件
    getChildData(data) {
      this.childData = data;
    },
    // 使用$chilren取子组件
    getChildByRef() {
      // 得到所有子组件的所有属性和方法 
      console.log("使用$children", this.$children);
    },
  },
};
</script>

3. $attrs$listeners

$attrs是在Vue2.4.0之后新提出的,通常在多层组件传递数据的时候使用。

$attrs的使用

$attrs是在Vue2.4.0之后新提出的,通常在多层组件传递数据的时候使用。 在父组件传递给第一级子组件,时用:自定义事件="数据"传递,第一级子组件用this.$attrs获取传递过来没用props接收的数据;若第一级子组件想向第二级组件传递数据,用在标签中添加v-bind="attrs"属性,同样第二级子组件用this.$attrs接收传递过来的数据,以此类推。

父组件

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <child1
      :msg="msg"
      :msg1="msg1"
      :msg2="msg2"
      :msg3="msg3"
      :msg4="msg4"
    ></child1>
  </div>
</template>
<script>
import child1 from "./child1.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      msg1: "parent数据1",
      msg2: "parent数据2",
      msg3: "parent数据3",
      msg4: "parent数据4",
    };
  },
  components: {
    child1
  }
};
</script>

第一级子组件

this.$attrs获取传递过来没用props接收的数据,并向第二级组件传递数据,在在标签中添加v-bind="attrs"属性

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <!-- 子组件child1-child -->
    <child1-child v-bind="$attrs"></child1-child>
  </div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
  components: {
    Child1Child,
  },
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  mounted() {
      // 返回父级传递过来没用props接收的数据
    console.log("child1组件获取$attrs", this.$attrs);
  }
};
</script>

image.png

上段代码中我们的parent父组件传递了5个数据给子组件:msg、msg1、msg2、msg3、msg4。但是在子组件中的props属性里面,我们只接收了msg。然后我们在子组件mounted中打印了$attrs,发现恰好少了props接收过的msg数据。

第二级子组件

// src/views/child1-child.vue
<template>
  <div class="child1-child">
    <p>我是孙子组件child1-child</p>
  </div>
</template>
<script>
export default {
  props: {
    msg1: {
      type: String,
      default: "",
    },
  },
  mounted() {
    // 返回第一级子组件没用props接收的数据
    console.log("child1-child组件$attrs", this.$attrs);
  },
};
</script>

image.png

我们发现child1-child组件中打印的$attrs中少了msg1,因为我们已经在props中接收了msg1。

$listeners 的使用

listeners属性和attrs属性和类型,只是它们传递的东西不一样。

listeners和attrs的区别很明显,attrs的区别很明显,attrs用来传递属性,$listeners用来传递非原生事件

当父组件在子组件上定义了一些自定义的非原生事件时,在子组件内部可以通过$listeners属性获取到这些自定义事件。

第一级子组件

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="sendData">传递数据给父组件</button>
    </div>
    <div>
      <button @click="getParentData">使用$parent</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
    <!-- 子组件child1-child -->
    <child1-child v-bind="$attrs" v-on="$listeners"></child1-child>
  </div>
</template>
<script>
mounted() {
  console.log("child1组件获取$attrs", this.$attrs);
  console.log("child1组件获取$listeners", this.$listeners);
}
</script>

image.png

可以发现输出了childData方法,这是我们在它的父组件自定义的监听事件。除此之外,$listeners可以通过v-on的形式再次传递给下层组件。

listeners的使用(用于修补listeners 的使用(用于修补attrs的bug)

我们在使用$attrs时,child1子组件渲染的DOM节点上将我们传递的属性一起渲染了出来

  • inheritAttrs解决未使用props接收的数据的属性渲染。

父组件传递了很多数据给子组件,子组件的props没有完全接收,那么父组件传递的这些数据就会渲染到HTML上,我们可以给子组件设置inheritAttrs 为false,避免这样渲染。

4. 自定义事件:事件总线

总线使用两个方法 $on 和 $emit 。一个用于创建发出的事件,它就是$emit ;另一个用于订阅 监听获取$on

事件总线使用时,一定要注意名字不能重名,尤其是多个组件共用的时候

这里用全局的

// src/main.js
Vue.config.productionTip = false
Vue.prototype.$EventBus = new Vue()
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

child1组件示例代码:

<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="toChild2">向child2组件发送数据</button>
    </div>
  </div>
</template>
<script>
export default {
  methods: {
    // 通过事件总线向child2组件发送数据
    toChild2() {
      this.$EventBus.$emit("sendMsg", "我是child1组件发来的数据");
    },
  },
};
</script>

child2组件2示例代码:

// src/views/child1.vue
<template>
  <div class="child-2">
    <p>child2组件</p>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  mounted() {
    this.$EventBus.$on("sendMsg", (msg) => {
      console.log("接收到child1发送来的数据", msg);
    });
  },
};
</script>

这里用的是局部

// src/utils/eventBus.js
improt bus from 'vue'
exprot default new Vue()

child1组件示例代码:

<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="toChild2">向child2组件发送数据</button>
    </div>
  </div>
</template>
<script>
improt bus from "@/utils/eventBus.js"
export default {
  methods: {
    // 通过事件总线向child2组件发送数据
    toChild2() {
      bus.$emit("sendMsg", "我是child1组件发来的数据");
    },
  },
};
</script>

child2组件2示例代码:

// src/views/child1.vue
<template>
  <div class="child-2">
    <p>child2组件</p>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
improt bus from "@/utils/eventBus.js"
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  mounted() {
   bus.$on("sendMsg", (msg) => {
      console.log("接收到child1发送来的数据", msg);
    });
  },
};
</script>

5. provide和inject

Vue2.2.0新增的API,provide和inject需要在一起使用。

父组件可以向子组件(无论层级)注入依赖,每个子组件都可以获得这个依赖,无论层级。

parent(爷爷)示例代码:

// src/views/parent.vue
<script>
import child1 from "./child1.vue";
export default {
  provide() {
    return { parentData: this.msg };
  },
  data() {
    return {
      msg: "我是父组件的数据",
    };
  },
  components: {
    child1
  },
};
</script>

child1-child(孙子)组件示例代码:

// src/views/child1-child.vue
<template>
  <div class="child1-child">
    <p>我是孙子组件child1-child</p>
    <p>parent组件数据:{{parentData}}</p>
  </div>
</template>
<script>
export default {
  inject: ["parentData"],
  props: {},
  mounted() {
    console.log("child1-child组件获取parent组件数据", this.parentData)
  },
};
</script>

image.png

如果想让传参,变成响应式的,就用如下方法写parent(爷爷)组件

// src/views/parent.vue
<script>
import child1 from "./child1.vue";
export default {
  provide() {
    return { parentData: this.getMsg };
  },
  data() {
    return {
      msg: "我是父组件的数据",
    };
  },
  components: {
    child1
  },
  methods: {
    // 返回data数据
    getMsg() {
      return this.msg;
    },
  },
};
</script>

image.png

6. Vuex和localStorage

这种方式应该是小伙伴们在实际项目中使用最多的

Vuex:

  • Vuex是状态管理器,它存储的数据不是持久化存储,一旦刷新页面或者关闭项目数据便不见了。
  • Vuex存储的数据是响应式的。

localstorage:

  • loacalStorage是HTML5中的一种数据存储方式,持久化存储,存储的数据不是响应式的。

总结

  • 父子组件间通讯:props和emit、emit、emit、parent、refs和refs和refs和children、v-model

  • 兄弟组件间通讯:事件总线、Vuex、localStorage

  • 隔代组件间通讯:provide和inject

  • 无相关组件间通讯:事件总线、Vuex、localStorage