vue 组件之间的通信

目录

前言

一、props 和 $emit

1、一般的通信

2、子组件更新父组件的变量

二、$attrs 和 $listeners

三、eventBus——$emit 和 $on

四、provide 和 inject(跨组件通信)

1、认识 provide 和 inject

2、provide 和 inject 的使用

(1)、传递普通的数据

 (2)、传递异步的数据

五、使用 vue-ref 插件结合 provide 和 inject 获取组件的实例

六、v-model

七、$parent、$children 和 $refs

1、$children 和 $rfes

2、$parent(不建议使用)

3、案例

八、vuex 处理组件之间的数据交互


前言

参考:vue组件之间8种组件通信方式总结

vue 组件之间的通信方式大概有 9 种:

  • props 和 $emit
  • $refs
  • $attrs 和 $listeners
  • eventBus
  • provide 和 inject
  • v-model
  • $parent 和 $children
  • boradcast 和 dispatch
  • vuex 处理组件之间的数据交互

一、props 和 $emit

父传子——props

子传父——$emit

1、一般的通信

Vue.component("child", {
    data() {
        return {
            mymessage: this.message,
        };
    },
    template: `
        <div>
            <input type="text" v-model="mymessage" @input="passData(mymessage)"> </div>
    `,
    props: ["message"], //得到父组件传递过来的数据
    methods: {
        passData(val) {
            //触发父组件中的事件
            this.$emit("getChildData", val);
        },
    },
});

Vue.component("parent", {
    template: `
        <div>
            <p>this is parent compoent!</p>
            <child :message="message" v-on:getChildData="getChildData"></child>
        </div>
    `,
    data() {
        return {
            message: "hello",
        };
    },
    methods: {
        //执行子组件触发的事件
        getChildData(val) {
            console.log(val);
        },
    },
});
var app = new Vue({
    el: "#app",
    template: `
        <div>
            <parent></parent>
        </div>
    `,
});

上述代码中,父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件;子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件。

假设有这么一个场景:私人订制一只宠物。父组件是具有定制宠物的给用户的功能,子组件具有决定定制什么宠物的功能。那么此时,子组件在调用父组件的定制宠物的给用户的功能时,就需要给选定一种宠物,这就需要传递第二个参数了。

2、子组件更新父组件的变量

vue 在子组件中更新父组件中传过来的变量——.sync 和 $emit(update:xxx)icon-default.png?t=N7T8https://blog.youkuaiyun.com/mChales_Liu/article/details/122193214

二、$attrs 和 $listeners

问题:如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?

可以借助 $attrs 和 $listeners 来实现 “隔代传值”。

Vue.component("C", {
    template: `
        <div>
            <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> </div>
    `,

    methods: {
        passCData(val) {
            //触发父组件A中的事件
            this.$emit("getCData", val);
        },
    },
});

Vue.component("B", {
    data() {
        return {
            mymessage: this.message,
        };
    },
    template: `
        <div>
            <input type="text" v-model="mymessage" @input="passData(mymessage)"> 
            <!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
            <!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
            <C v-bind="$attrs" v-on="$listeners"></C>
        </div>
    `,
    props: ["message"], //得到父组件传递过来的数据
    methods: {
        passData(val) {
            //触发父组件中的事件
            this.$emit("getChildData", val);
        },
    },
});

Vue.component("A", {
    template: `
        <div>
            <p>this is parent compoent!</p>
            <B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
        </div>
    `,
    data() {
        return {
            message: "hello",
            messagec: "hello c", //传递给c组件的数据
        };
    },
    methods: {
        getChildData(val) {
            console.log("这是来自B组件的数据");
        },
        //执行C子组件触发的事件
        getCData(val) {
            console.log("这是来自C组件的数据:" + val);
        },
    },
});

var app = new Vue({
    el: "#app",
    template: `
        <div>
            <A></A>
        </div>
    `,
});

三、eventBus——$emit 和 $on

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。

$emit 和 $on 的实现:新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。

Vue.component("brother1", {
    data() {
        return {
            mymessage: "hello brother1",
        };
    },
    template: `
        <div>
            <p>this is brother1 compoent!</p>
            <input type="text" v-model="mymessage" @input="passData(mymessage)"> 

        </div>
    `,
    methods: {
        passData(val) {
            //触发全局事件globalEvent
            bus.$emit("globalEvent", val);
        },
    },
});

Vue.component("brother2", {
    template: `
        <div>
            <p>this is brother2 compoent!</p>
            <p>brother1传递过来的数据:{{brothermessage}}</p>
        </div>
    `,
    data() {
        return {
            mymessage: "hello brother2",

            brothermessage: "",
        };
    },
    mounted() {
        //绑定全局事件globalEvent
        bus.$on("globalEvent", (val) => {
            this.brothermessage = val;
        });
    },
});

//中央事件总线
var bus = new Vue();

var app = new Vue({
    el: "#app",
    template: `
        <div>
            <brother1></brother1>
            <brother2></brother2>
        </div>
    `,
});

四、provide 和 inject(跨组件通信)

provide 和 inject 是成对出现的。在上层组件中通过 provide 来提供变量,然后在下层组件中通过 inject 来注入变量。用于父组件向子孙组件传递数据。

1、认识 provide 和 inject

provide 选项可以是:

  • 一个对象。
  • 一个函数,该函数的返回值是一个对象。

inject 选项可以是:

  • 一个字符串数组。
  • 一个对象。此时:
    • 对象的 key 是当前组件将要使用的变量名。
    • 对象的 value 可以是:
      • provide 里提供的属性,以及。
      • 一个对象,该对象包括两个属性:
        • from 属性:provide 里提供的属性。
        • default 属性:默认值。

provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

2、provide 和 inject 的使用

(1)、传递普通的数据

父组件:

<template>
  <s-p-r-v2 />
</template>
<script>
  import SPRV2 from './components/s-p-r-v2.vue'

  export default {
    components: {
      SPRV2
    },
    provide() {
      return {
        str: 'hello'
      }
    }
  }
</script>

子组件:

<template>
  <!-- <div>{{ str }}</div> -->
  <div>{{ msg }}</div>
</template>
<script>
  export default {

    // 方式一
    // inject: ['str'],

    // 方式二
    // inject: {msg: 'str' }

    // 方式三
    inject: {
      msg: {
        from: 'str',
        default: ''
      }
    }
  }
</script>

 (2)、传递异步的数据

父组件:

<template>
  <s-p-r-v2 />
</template>
<script>
  import SPRV2 from './components/s-p-r-v2.vue'

  export default {
    components: {
      SPRV2
    },
    provide() {
      return {
        flag: () => this.flag
      }
    },
    data() {
      return {
        flag: false
      }
    },
    mounted() {
      setTimeout(() => {
        this.flag = true
      }, 3000)
    }
  }
</script>

子组件:

<template>
  <div>{{ newStatus }}</div>
</template>
<script>
  export default {

    // 方式一
    // inject: ['flag'],

    // 方式二
    // inject: { status: 'flag' },

    // 方式三
    inject: {
      status: {
        from: 'flag',
        default: () => false
      }
    },
    computed: {
      newStatus() {
        // return this.flag()
        return this.status()
      }
    }
  }
</script>

【注意】小型状态管理,可以使用 provide/inject。大型状态管理,建议使用 vuex。

 【扩展】 

vue中的provide和inject

vue中provide和inject使用

五、使用 vue-ref 插件结合 provide 和 inject 获取组件的实例

可以使用 ref 来获取跨级组件实例。

安装:vue-ref

npm i vue-ref -S

然后,在main.js中写入下面的代码:

import ref from 'vue-ref';
Vue.use(ref, { name: 'getRef' });// 这里之所以重命名,是为了避免与vue预留的v-ref指令冲突

 假设,已经创建了vue实例,并且在其中引入了index.vue,或者配置好了vue-router。

// index.vue
<template>
  <div>
    <ChildrenA />
  </div>
</template>
<script>
import ChildrenA from "./component/ChildrenA1";
export default {
  components: {
    ChildrenA
  }
};
</script>

 子组件:A

// ChildrenA1.vue
<template>
  <div>
    <h1>A 结点</h1>
    <button @click="getEH3Ref">获取E h3 Ref</button>
    <ChildrenC />
  </div>
</template>
<script>
import ChildrenC from "./ChildrenC2";
export default {
  components: {
    ChildrenC,
  },
  provide() {
    return {
      /**
       * @name:子组件的组件名称
       * @ref:子组件中 ref 的名字
       * 
       * 主动通知——缓存子组件的 ref
       **/ 
      setChildrenRef: (name, ref) => {
        this[name] = ref;
      },
      /**
       * @name:其他子组件的组件名称
       * 
       * 主动获取——获取其他子组件的实例
       **/ 
      getChildrenRef: name => {
        return this[name];
      },
      // 获取上层组件的实例
      getRef: () => {
        return this;
      }
    };
  },
  methods: {
    getEH3Ref() {
      console.log(this.childrenE);
    }
  }
};
</script>

子组件的子组件:E

// childrenE.vue
<template>
  <div>
    <h3 v-getRef="c => setChildrenRef('childrenE', c)">E 结点</h3>
    <button @click="getARef">获取组件 A 的实例</button>
    <button @click="getHRef">获取组件 H 的实例</button>
  </div>
</template>
<script>
export default {
  inject: {
    setChildrenRef: {
      default: null
    },
    getParentRef: {
      from: "getRef",
      default: () => {}
    },
    getParentChildrenRef: {
      from: "getChildrenRef",
      default: () => {}
    }
  },
  methods: {
    // 获取上层组件的实例
    getARef() {
      console.log(this.getParentRef());
    },
    // 获取其他子组件的实例
    getHRef() {
      console.log(this.getParentChildrenRef("childrenH"));
    }
  }
};
</script>

 子组件的子组件:H

<template>
  <div>
    <h3>H 结点</h3>
  </div>
</template>

六、v-model

父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值。

Vue.component("child", {
    props: {
        value: String, //v-model会自动传递一个字段为value的prop属性
    },
    data() {
        return {
            mymessage: this.value,
        };
    },
    methods: {
        changeValue() {
            this.$emit("input", this.mymessage); //通过如此调用可以改变父组件上v-model绑定的值
        },
    },
    template: `
        <div>
            <input type="text" v-model="mymessage" @change="changeValue"> 
        </div>
    `,
});

Vue.component("parent", {
    template: `
        <div>
            <p>this is parent compoent!</p>
            <p>{{message}}</p>
            <child v-model="message"></child>
        </div>
    `,
    data() {
        return {
            message: "hello",
        };
    },
});

var app = new Vue({
    el: "#app",
    template: `
        <div>
            <parent></parent>
        </div>
    `,
});

注意:在组件上使用 v-model

七、$parent、$children 和 $refs

有时候我们需要父组件直接访问子组件、子组件直接访问父组件,或者子组件访问根组件:

  • 父组件访问子组件:使用 $children 或 $refs
  • 子组件访问父组件:使用 $parent

1、$children 和 $rfes

this.$children 是一个数组类型,它包含了所有子组件对象,可以通过循环它拿到所有子组件数据。

比如:this.$children[2] 来拿到第三个子组件的数据。
但是这么做有一个问题:比如开发时突然在这三个子组件中又插入了一个子组件(可能相同,也可能不同),这时候 this.$children[2] 就不再是我们需要的了。怎么办呢?使用 $rfes。

2、$parent(不建议使用)

$parent 是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。该属性只读。

不建议使用 $parent,因为它的耦合度很高,容易发生一些错误。

3、案例

Vue.component("child", {
    props: {
        value: String, //v-model会自动传递一个字段为value的prop属性
    },
    data() {
        return {
            mymessage: this.value,
        };
    },
    methods: {
        changeValue() {
            this.$parent.message = this.mymessage; //通过如此调用可以改变父组件的值
        },
    },
    template: `
        <div>
            <input type="text" v-model="mymessage" @change="changeValue"> 
        </div>
    `,
});

Vue.component("parent", {
    template: `
        <div>
            <p>this is parent compoent!</p>
            <button @click="changeChildValue">test</button >
            <child></child>
        </div>
    `,
    methods: {
        changeChildValue() {
            this.$children[0].mymessage = "hello";
        },
    },
    data() {
        return {
            message: "hello",
        };
    },
});

var app = new Vue({
    el: "#app",
    template: `
        <div>
            <parent></parent>
        </div>
    `,
});

八、vuex 处理组件之间的数据交互

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。


详情可参考:Vuex

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值