美文网首页
VUE的通信方式

VUE的通信方式

作者: 青城墨阕 | 来源:发表于2022-06-29 20:09 被阅读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传递数据

    • 适用场景:父组件传递数据给子组件
    • 使用方式:
              子组件设置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

    相关文章

      网友评论

          本文标题:VUE的通信方式

          本文链接:https://www.haomeiwen.com/subject/wncxbrtx.html