VUE的通信方式

2022-06-29  本文已影响0人  青城墨阕

前言

本章总结了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传递数据

// 父组件
<Children name="jack" age=18 />

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

2. $emit 触发自定义事件

// 父组件
<Children @add="cartAdd" /> 

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

3. ref

// 父组件
<Children ref="foo" />  
this.$refs.foo  // 获取子组件实例,通过子组件实例我们就能拿到对应的数据及函数

4. 作用域插槽(slot)

5. EventBus

// 创建一个中央时间总线类  
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

// 兄弟组件
this.$parent.on('add',this.add);

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

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


7. $attrs$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

// 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传递数据

// 父组件
<child msg="hello"/>

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

2. 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

<!-- 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

6. $parent$root$children

vue2.0 vue3.0
this.$parent.父组件的方法名/父组件的属性名 import {getCurrentInstance} from 'vue';

const {proxy} = getCurrentInstance();

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

7. $attrs$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

// 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

上一篇 下一篇

猜你喜欢

热点阅读