VUE的通信方式

前言

本章总结了vue2.x与vue3.x的通信方式


VUE2.x的通信方式


  1. props传递数据
  2. 通过 $emit 触发自定义事件
  3. 使用 $ref
  4. 作用域插槽(slot)
  5. eventBus
  6. $parent(或$root)与$children
  7. $attrs$listeners
  8. provideinject
  9. Vuex

适合父子间通信props$emit$refslot$parent$children
适合兄弟组件之间的通信eventBusVuex
祖孙与后代组件之间的通信$attrs/$listenersprovide/injecteventBusVuex
复杂关系的组件之间的通信Vuex


1. props传递数据

  • 适用场景:父组件传递数据给子组件
  • 使用方式:
            子组件设置props属性,定义接收父组件传递过来的参数
            父组件在使用子组件标签中通过字面量来传递值
// 父组件
<Children name="jack" age=18 />

// 子组件
props:{    
    name:String // 接收的类型参数
    age:{    
        type:Number, // 接收的类型为数值  
        defaule:18  // 默认值为18
    }  
}  


2. $emit 触发自定义事件

  • 适用场景:子组件传递数据给父组件
  • 使用方式:
            子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
            父组件绑定监听器获取到子组件传递过来的参数
// 父组件
<Children @add="cartAdd" /> 

// 子组件
this.$emit('add', 'good')  


3. ref

  • 适用场景:父组件需要获取到子组件时
  • 使用方式:
            父组件在使用子组件的时候设置ref
            父组件通过设置子组件ref来获取数据
// 父组件
<Children ref="foo" />  
this.$refs.foo  // 获取子组件实例,通过子组件实例我们就能拿到对应的数据及函数


4. 作用域插槽(slot)

5. EventBus

  • 适用场景:兄弟组件/隔代组件之间的通信
  • 使用方式:
            创建一个中央时间总线EventBus
            一个组件通过$on监听自定义事件
            另一个组件通过$emit触发事件,$emit第二个参数为传递的数值
// 创建一个中央时间总线类  
class Bus {  
  constructor() {  
    this.callbacks = {};   // 存放事件的名字  
  }  
  $on(name, fn) {  
    this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {  
    if (this.callbacks[name]) {  
      this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  
  
// main.js  
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
// 一个组件监听自定义事件
this.$bus.$on('foo', function() {});

// 另一个组件触发事件
this.$bus.$emit('foo')  


6. $parent$root$children

  • 适用场景:通过共同祖辈parent或者root搭建通信侨联
  • 兄弟组件之间:
// 兄弟组件
this.$parent.on('add',this.add);

// 另一个兄弟组件
this.$parent.emit('add');
  • 父子组件之间:
// 子组件获取到子组件
this.$parent

// 同理,父组件获取到子组件,但是是个列表
this.$children



7. $attrs$listeners

  • 适用场景:祖先传递属性(可以是值,可以是函数)给子孙
  • $attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
  • $listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
<!-- father.vue 组件:-->
<template>
   <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
    import Child from '../components/child.vue'

    export default {
        name: 'father',
        components: { Child },
        data () {
            return {
                name: 'Lily',
                age: 22,
                infoObj: {
                    from: '上海',
                    job: 'policeman',
                    hobby: ['reading', 'writing', 'skating']
                }
            }
        },
        methods: {
            updateInfo() {
                console.log('update info');
            },
            delInfo() {
                console.log('delete info');
            }
        }
    }
</script>


<!-- child.vue 组件:-->
<template>
    <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners"  />
    // 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
    import GrandSon from '../components/grandSon.vue'
    export default {
        name: 'child',
        components: { GrandSon },
        props: ['name'],
        data() {
          return {
              height: '180cm',
              weight: '70kg'
          };
        },
        created() {
            console.log(this.$attrs); 
       // 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
            console.log(this.$listeners); // updateInfo: f, delInfo: f
        },
        methods: {
            addInfo () {
                console.log('add info')
            }
        }
    }
</script>

<!-- grandSon.vue 组件:-->
<template>
    <div>
        {{ $attrs }} --- {{ $listeners }}
    <div>
</template>
<script>
    export default {
        ... ... 
        props: ['weight'],
        created() {
            console.log(this.$attrs); // age, infoObj, height 
            console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
            this.$emit('updateInfo') // 可以触发隔代组件father中的updateInfo函数

        }
    }
</script>

简易版例子:

// 给Grandson隔代传值,communication/index.vue  
<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>  
  
// Child2做展开  
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>  
  
// Grandson使⽤  
<div @click="$emit('some-event', 'msg from grandson')">  
{{msg}}  
</div>  


8. provideinject

  • 适用场景:祖先传递值给子孙
// 祖先组件
provide(){  
    return {  
        foo:'foo'  
    }  
}  

// 后代组件
inject:['foo'] // 获取到祖先组件传递过来的值  


9. vuex

  • 适用场景: 复杂关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器
    state:包含了store中存储的各个状态。
    getter: 类似于 Vue 中的计算属性,根据其他 getter 或 state 计算返回值。
    mutation: 一组方法,是改变store中状态的执行者,只能是同步操作。
    action: 一组方法,其中可以包含异步操作。
// main.js
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0,
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getter: {
    doneTodos: (state, getters) => {
      return state.todos.filter(todo => todo.done)
    }
  },
  mutations: {
    increment (state, payload) {
      state.count++
    }
  },
  actions: {
    addCount(context) {
      // 可以包含异步操作
      // context 是一个与 store 实例具有相同方法和属性的 context 对象
    }
  }
})
// 注入到根实例
new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  template: '<App/>',
  components: { App }
});


// 在子组件中使用
// 创建一个 Counter 组件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    },
    doneTodosCount () {
      return this.$store.getters.doneTodosCount
    }
  }
}




VUE3.0的通信方式


  1. props传递数据
  2. 通过 emit 触发自定义事件
  3. 使用 ref
  4. 作用域插槽(slot)
  5. mitt(eventBus)
  6. $parent(或$root)与$children
  7. $attrs$listeners
  8. provideinject
  9. Vuex

适合父子间通信propsemitrefslotparentchildren
适合兄弟组件之间的通信mitt(eventBus)、Vuex
祖孙与后代组件之间的通信$attrs/$listenersprovide/injectmitt(eventBus)、Vuex
复杂关系的组件之间的通信Vuex

1. props传递数据

  • 适用场景:父组件向子组件传递数据
  • 使用方式:
            父组件在使用子组件标签中通过字面量来传递值(与VUE2.x方式相同)
            与VUE2.x相同的是子组件也设置props属性配置接受的数据,不同的是vue3.0的prop为setup的第一个参数
// 父组件
<child msg="hello"/>

// 子组件
export default defineComponent({
    props: { // 配置要接受的props
        msg: {
            type: String,
            default: () => ''
        }
    },
    setup(props) {
        console.log('props ====', props);
    },
});


2. emit 触发自定义事件

  • 适用场景:子组件传递数据给父组件
  • 使用方式:
            父组件绑定监听器获取到子组件传递过来的参数(与VUE2.x方式相同)
            与VUE2.x不同的是:子组件通过context.emit触发自定义事件(contextsetup的第二个参数,为上下文),相同的是:emit第二个参数为传递的数值
<!-- Father.vue -->
<template>
    <div style="width: 500px; height: 500px; padding: 20px; border: 2px solid #ff00ff;">
        <div>Father:</div>
        <child msg="hello" @todoSth="todoSth"/>
    </div>
</template>
<script lang="ts">
import {
    defineComponent
} from 'vue';
import child from './components/child.vue';
export default defineComponent({
    components: {
        child
    },
    setup() {
        // 父组件绑定监听器获取到子组件传递过来的参数
        const todoSth = (text: string) => {
            console.log('text: ', text);
        };
        return {
            todoSth
        };
    },
});
</script>


<!-- Child.vue -->
<template>
    <div style="width: 100%; height: calc(100% - 40px); padding: 20px; box-sizing: border-box; border: 2px solid #00ff00;">
        <div>Child:</div>
        <button @click="setMsg2Parent">点击按钮给父组件传递信息</button>
    </div>
</template>
<script lang="ts">
import {
    defineComponent
} from 'vue';
export default defineComponent({
    setup(props, context) {
        const setMsg2Parent = () => {
            context.emit('todoSth', 'hi parent!');
        };
        return {
            setMsg2Parent
        };
    },
});
</script>



3. ref

  • 适用场景:父组件需要获取到子组件时
  • 使用方式:
            父组件在使用子组件的时候设置ref(与VUE2.x方式相同)
            父组件通过设置子组件ref来获取数据
    注:: 1.获取子组件时变量名必须与子组件的ref属性保持一致;2.必须将获取到的组件return。
<!-- Father.vue -->
<template>
    <div style="width: 500px; height: 500px; padding: 20px; border: 2px solid #ff00ff;">
        <div>Father:</div>
        <button @click="getChildCom">获取到子组件并调用其属性</button>
        <child ref="childCom" msg="hello"/>
    </div>
</template>
<script lang="ts">
import {
    defineComponent,
    ref
} from 'vue';
import child from './components/child.vue';
export default defineComponent({
    components: {
        child
    },
    setup() {
        const childCom = ref<HTMLElement>(null);
        // 获取到子组件并调用其属性
        const getChildCom = () => {
            childCom.value.childText = '父组件想要改变这个属性!';
            childCom.value.childMethod1();
        };
        return {
            childCom,
            getChildCom
        };
    },
});
</script>


<!-- Child.vue -->
<template>
    <div style="width: 100%; height: calc(100% - 40px); padding: 20px; box-sizing: border-box; border: 2px solid #00ff00;">
        <div>Child:</div>
        <p>{{childText}}</p>
    </div>
</template>
<script lang="ts">
import {
    defineComponent,
    reactive,
    toRefs
} from 'vue';
export default defineComponent({
    props: {
        msg: {
            type: String,
            default: () => ''
        }
    },
    setup(props, context) {
        const currStatus = reactive({
            childText: '我是一个子组件~~~'
        });
        const childMethod1 = () => {
            console.log('这是子组件的一个方法');
        };
        return {
            childMethod1,
            ...toRefs(currStatus)
        };
    },
});
</script>

事件调用前

事件调用后


4. 作用域插槽(slot)

5. mitt

  • 适用场景:兄弟组件/隔代组件之间的通信
  • Vue 3 移除了 $on$off$once 这几个事件 API ,应用实例不再实现事件触发接口。
  • 根据官方文档在 迁移策略 - 事件 API 的推荐,我们可以用 mitt 或者 tiny-emitter 等第三方插件来实现 EventBus

6. $parent$root$children

  • 适用场景:通过共同祖辈parent或者root搭建通信侨联
  • $children已被废弃
  • $parent 使用方式与vue2.x有所区别($root同理)
vue2.0 vue3.0
this.$parent.父组件的方法名/父组件的属性名 import {getCurrentInstance} from 'vue';

const {proxy} = getCurrentInstance();

proxy.$parent.父组件的方法名/父组件的属性名


7. $attrs$listeners

  • 适用场景:祖先传递属性(可以是值,可以是函数)给子孙
  • 使用方式与vue2.0一致
  • $attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
  • $listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
<!-- Father.vue -->
<child msg="hello" attrsText="测试attrsText" @listenersFun="listenersFun"/>
export default defineComponent({
    components: {
        child
    },
    setup(props, context) {
        const listenersFun = (msg: string) => {
            console.log(msg);
        }
        return {
            listenersFun
        };
    },
});


<!-- Child.vue -->
<grandsun v-bind="$attrs" v-on="$listeners"/>
export default defineComponent({
    components: {
        grandsun
    },
     // 子组件的props没有配置attrsText,故attrsText存在context.attrs中
    props: {
        msg: {
            type: String,
            default: () => ''
        }
    },
});

<!-- Grandsun.vue -->
export default defineComponent({
    setup(props, context) {
       console.log('通过attrs获取祖父的属性值:', context.attrs.attrsText)
       context.emit('listenersFun', '通过listeners跨级调用祖父的函数');
    },
});



8. provideinject

  • 适用场景:祖先传递值给子孙
  • 使用方式与vue2.0略有差异,vue2.0的provide和inject都为配置项,而在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。
// father.vue
import { provide } from 'vue';
// provide出去(key, value)
provide('msg', '祖父传递一个属性给子孙');


// Grandsun.vue
import { inject } from 'vue';
console.log('通过inject获取祖父的属性值:', inject('msg'))










参考:
https://segmentfault.com/a/1190000022708579
https://juejin.cn/post/6844903990052782094#heading-0
https://vue3.chengpeiquan.com/communication.html#%E7%88%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1
https://blog.csdn.net/qq_15601471/article/details/122032034

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容