6

详细讲解vue中祖孙组件间的通信之使用$attrs和$listeners的方式(篇幅略长,建议收藏...

 2 years ago
source link: https://segmentfault.com/a/1190000040317732
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  • vue实例bus事件

    • vue实例bus其实不仅仅可以用在祖孙组件间通信,也可以用在兄弟组件间通信,使用范围还是挺广泛的,关于vue实例bus的用法,笔者之前也写过一篇文章,是以兄弟组件间通信为例的。链接附上:https://segmentfault.com/a/11...
  • $attrs和$listeners

    • 这种方式也还可以的,请继续往下阅读

官方定义$attrs和$listeners

我们先看一下官方是如何定义的,截图奉上:

官方地址也附上吧 https://cn.vuejs.org/v2/api/?...

吐槽一下,官方的定义略微有些晦涩难懂。下面我们将结合实际的例子,来解释$attrs和$listeners的用法。所以我们需要先搭建一个项目,结构就是祖孙组件数据传递

项目结构图

我们知道项目的最外层的vue组件就是App.vue组件,我们把App.vue组件当做爷组件、而对应fu.vue就是父组件,同时sun.vue就是孙子组件。也就是爷、父、子的这种祖孙关系组件。我们在这样的结构中去实现爷组件到孙子组件中的数据传递。

$attrs的用法

$attrs我的理解就是:

  • 正常情况下:父组件通过v-bind绑定一个数据传递给子组件,子组件通过props接收到就可以在子组件的html中使用了。但是,如果父组件v-bind传递给子组件,子组件没有用props接收呢?
  • 注意:这个时候,父组件传递过来的数据就会被挂载(赋值)到这个子组件自带的对象$attrs上面,所以:
  • $attrs就是一个容器对象,这个容器对象会存放:父组件传过来的且子组件未使用props声明接收的数据

代码层面理解

我们使用上述搭建的项目,把App.vue当做父组件,fu.vue当做子组件。(实际上,项目中这二者分别是爷组件、父组件,不过爷父组件,其实也是父子组件的关系,也可以用)
爷组件代码
在爷组件中,我们给父组件传递4个数据,msg1、msg2、msg3、msg4,其数据类型分别是字符串、字符串、数组、对象

<template>
  <div id="app">
    我是爷组件
    <fu
      :msg1="msg1"
      :msg2="msg2"
      :msg3="msg3"
      :msg4="msg4"
    ></fu>
  </div>
</template>

<script>
import fu from "./views/fu.vue";
export default {
  components: {
    fu,
  },
  data() {
    return {
      msg1: "孙悟空",
      msg2: "猪八戒",
      msg3: ["白骨精", "玉兔精", "狐狸精"],
      msg4: {
        name: "炎帝萧炎",
        book: "斗破苍穹",
      },
    };
  },
};
</script>
<style lang="less" scoped>
#app {
  width: 950px;
  height: 600px;
  box-sizing: border-box;
  border: 3px dashed #e9e9e9;
  background-color: #cde;
  margin: 50px;
}
</style>

父组件代码
在父组件中我们只在props中接收msg1,另外三个我们不在props中接收。于是另外三个未在props中接收的,会自动被存放在$attrs这个容器对象中去。同时,我们通过$attrs对象也可以拿到对应的爷组件中传递过来的,未在props中接收的数据值,也可以在html中使用。

<template>
  <div class="fatherClass">
    我是父组件
    <h2>{{ msg1 }}</h2>
    <h2>{{ $attrs.msg2}}</h2>
    <h2>{{ $attrs.msg3}}</h2>
    <h2>{{ $attrs.msg4}}</h2>
  </div>
</template>

<script>
export default {
  name: "DemoFather",
  props: {
    msg1: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log('fu组件实例',this);
  },
};
</script>
<style lang="less" scoped>
.fatherClass {
  width: 850px;
  height: 400px;
  background-color: #baf;
  margin-left: 50px;
  margin-top: 50px;
}
</style>

我们先看一下上述爷父组件代码最终的效果图:

的确是fu.vue组件中未在props中声明接收的爷组件传递过来的数据,都存放在$attrs这个对象里面了。为了更直观的看到效果,我们可以在mounted钩子中打印this组件实例,在这个实例上,我们也可以看到$attrs中的存放的数据。打印效果图如下:

由此,验证了上述那句话:
$attrs就是一个容器对象,这个容器对象会存放:父组件传过来的且子组件未使用props声明接收的数据
那这个和我们的祖孙组件之间的数据传递有关系吗?
有关系,关系很大!

爷组件传递给孙组件的逻辑流程

其实,爷组件传递给孙组件的逻辑流程就是,通过爷组件首先传递给父组件,当然父组件不在props中接收,那么爷组件传递给父组件的数据就会存放到父组件的$attrs对象中里面了,然后,再通过v-bind="$attrs",再把这个$attr传递给孙组件,在孙组件中使用props就能接收到$attrs中的数据了,这样就实现了,祖孙之间的数据传递

祖孙之间的数据传递,需要通过中间的父组件$attrs做一个桥梁。其实就是这个意思。

再加一个孙组件

<template>
  <div class="sunClass">
    我是孙子组件
    <h2>接收爷组件数据:-->{{ msg2 }}</h2>
    <h2>接收爷组件数据:-->{{ msg3 }}</h2>
    <h2>接收爷组件数据:-->{{ msg4 }}</h2>
  </div>
</template>

<script>
export default {
  // $attrs一般搭配interitAttrs 一块使用
  inheritAttrs: false, // 默认会继承在html标签上传递过来的数据,类似href属性的继承
  /*
    孙子组件通过props,就能接收到父组件传递过来的$attrs了,就能拿到里面的数据了,也就是:
    爷传父、父传子。即:祖孙之间的数据传递。
  */ 
  props: {
    msg2: {
      type: String,
      default: "",
    },
    msg3: {
      type: Array,
      default: () => {
        return [];
      },
    },
    msg4: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  name: "DemoSun",
};
</script>

<style lang="less" scoped>
.sunClass {
  width: 750px;
  height: 180px;
  background-color: #bfa;
  margin-top: 80px;
  margin-left: 50px;
}
</style>

祖传孙最终效果图

呐,祖传孙实现啦...

$attrs一般搭配interitAttrs 一块使用,一般是inheritAttrs: false, // 默认会继承在html标签上传递过来的数据.这个大家审查一下DOM元素就能看到了。

$listeners的用法

使用$listeners可以实现孙组件的数据传递到爷组件中去,逻辑的话,也是用在中间的桥梁父组件上面去,我的理解就是$listeners可以将子组件emit的方法通知到爷组件。代码如下:

第一步,在中间的父组件中加上$listenners

<sun v-bind="$attrs" v-on="$listeners"></sun>

第二步,爷组件中定义事件方法

<template>
  <div id="app">
    我是爷组件
    <h3>{{ fromSunData }}</h3>
    <fu :msg1="msg1" :msg2="msg2" :msg3="msg3" :msg4="msg4" 
        @fromSun="fromSun">
    </fu>
  </div>
</template>

<script>
import fu from "./views/fu.vue";
export default {
  components: {
    fu,
  },
  data() {
    return {
      msg1: "孙悟空",
      msg2: "猪八戒",
      msg3: ["白骨精", "玉兔精", "狐狸精"],
      msg4: {
        name: "炎帝萧炎",
        book: "斗破苍穹",
      },
      fromSunData: "",
    };
  },
  methods: {
    fromSun(payload) {
      console.log("孙传祖", payload);
      this.fromSunData = payload;
    },
  },
};
</script>

比如这里定义一个fromSun的事件方法,可供孙组件emit触发。

第三步,孙组件去触发爷组件的事件方法即可

<template>
  <div class="sunClass">
    我是孙子组件
    <h2>接收爷组件:-->{{ msg2 }}</h2>
    <h2>接收爷组件:-->{{ msg3 }}</h2>
    <h2>接收爷组件:-->{{ msg4 }}</h2>
    <el-button size="small" type="primary" plain @click="sendToZu">孙传祖</el-button>
  </div>
</template>

<script>
export default {
  // $attrs一般搭配interitAttrs 一块使用
  inheritAttrs: false, // 默认会继承在html标签上传递过来的数据,类似href属性的继承
  props: {
    msg2: {
      type: String,
      default: "",
    },
    msg3: {
      type: Array,
      default: () => {
        return [];
      },
    },
    msg4: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  name: "DemoSun",
  data() {
    return {
      data: "来自孙组件的数据",
    };
  },
  methods: {
    sendToZu() {
      // 孙组件能够触发爷组件的fromSun方法的原因还是因为父组件中有一个$listeners作为中间人,去转发这个事件的触发
      this.$emit("fromSun", this.data);
    },
  },
};
</script>

比如这里我们在孙组件中点击按钮,将孙组件中的数据传递到爷组件中去。

孙传祖效果图

好记性不如烂笔头,记录一下吧。欢迎各位大佬批评指正,顺带关个注,点个赞,鼓励一下呗。嘿嘿


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK