阅读 951

VUE组件间通信方式总结

在面试中经常被问到,工作实际中也常常使用到,立马来篇博客总结记录

props和$emit

父组件向子组件传值

在父组件中通过props来把数据传入子组件

  • 父组件中

    在子组件标签中添加自定义属性,并通过 v-bind 动态赋值绑定要传给子组件的值

<template>
  <div class="parent">
    <child :message="childData"></child>
  </div>
</template>

<script>
import Child from './child'
export default {
  components: { Child },
  data() {
    return {
      childData: "父传子"
    }
  }
}
</script>
复制代码
  • 子组件中

    通过props选项来拿到父组件传输过来的值,可对值的类型和初始值进行定义

<template>
  <div>
    <span>{{message}}</span>
  </div>
</template>

<script>
export default {
  props:{
      message:{
         type:String,
          default: () => ''
      }
  }
}
</script>
复制代码

子组件向父组件传值

因为Vue是单向数据流,所以不能直接在子组件中修改父组件传输过来的值,而是子组件通过$emit来触发事件告知父组件去修改值

  • 子组件中

    在相关事件触发的函数中,通过$emit来告知父组件更改数据,第一个参数是父组件中子组件标签上自定义事件的名字,第二个参数是传给父组件的值,也就是父组件中自定义事件触发函数的参数。

<template>
  <div>
    <span>{{message}}</span>
    <button @click="sendMsgToParent">
       子传父
    </button>
  </div>
</template>

<script>
export default {
  props:{
      message:{
         type:String,
          default: () => ''
      }
  },
  methods:{
       sendMsgToParent(){
            this.$emit("listenChildEvent","this data is from child");
        }
    }
}
</script>
复制代码
  • 父组件中

    父组件中通过自定义事件绑定的函数来拿到子组件传递过来的data,进行相关值的操作

    <template>
        <div class="parent">
            <child :message="childData" @listenChildEvent="acceptMsgFromChild"></child>
        </div>
    </template>
    <script>
        import child from './child.vue';
        export default {
            data() {
                return {
                    childData: "父传子"
                };
            },
            components: {
                Child
            },
            methods:{
                acceptMsgFromChild(data){
                    console.log(data);//或者将接受值处理
                }
            }
        }
    </script>
复制代码

v-model

v-model来做表单双向数据绑定,v-model="msg"实则是 :value="msg" @input="msg = $event.target.value"的语法糖。所以v-model指令同时做了两件事:

1.监听input输入事件 2.将输入的值绑定到对应数据上

了解v-model的自带input事件 ,在父组件中通过v-model来绑定相关数据,子组件就可以通过$emit来触发input事件,将值传递给父组件。

  • 父组件中
    <template>
        <div class="parent">
            <child v-model="data"></child>
        </div>
    </template>
    <script>
        import child from './child.vue';
        export default {
            data() {
                return {
                    data: "父子间v-model通信"
                };
            },
            components: {
                Child
            }
        }
    </script>
复制代码
  • 子组件中

1.通过点击事件来触发父子组件数据同步

2.父组件中 v-model 绑定的值,只能通过value 这个属性名来传输

3.通过$emit来触发父组件v-model的input事件,第二个参数就是传给父组件将要更新的值

<template>
  <div>
    <span>{{value}}</span>
    <button @click="shareMsgToParent">数据同步</button>
  </div>
</template>
<script>
export default {
  props:{
      value:{
         type:String,
          default: () => ''
      }
  },
  methods:{
       shareMsgToParent(){
            this.$emit("input","this data is from child");
        }
    }
}
</script>
复制代码

上面的操作限制了我们必须props接收的属性名为value和emit触发的必须为input,这样就容易有冲突,所以,为了更优雅的使用v-model通信而解决冲突的问题,我们可以通过在子组件中使用model选项,来定义属性名和要触发的事件名

  • 子组件中使用v-model
<template>
  <div>
    <span>{{checked}}</span>
    <button @click="shareMsgToParent">数据同步</button>
  </div>
</template>

<script>
export default {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props:{
      value:{
         type:String,
          default: () => ''
      }
  },
  methods:{
       shareMsgToParent(){
            this.$emit("change","this data is from child");
        }
    }
}
</script>
复制代码

PS: 双向数据绑定的方法还有.sync 在另外一篇博客中有总结查看

$attrs$listeners

可以将父组件的数据传递给子孙组件,适用于只传递数据不做中间件处理的时候,但在项目中很少用到,简单了解即可。

  • $attrs

包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。

  • $listeners

包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。

简单来说$attrs是用来获取父组件中的属性数据的,$listeners是用来获取父组件中的事件

  • 父组件
<template>
    <child :name="name" :message="message" @sayHello="sayHello"></child>
</template>
<script>
export default {
    inheritAttrs: false,
    data() {
        return {
            name: '通信',
            message: 'Hi',
        }
    },
    methods: {
        sayHello(mes) {
            console.log('mes', mes) // => "hello"
        },
    },
}
</script>
复制代码
  • 子组件
<template>
    <grandchild v-bind="$attrs" v-on="$listeners"></grandchild>
</template>
<script>
export default {
    data() {
        return {}
    },
    props: {
        name,
    },
    created() {
            console.log(this.$attrs); 
       // 结果: message, 因为父组件共传来name, message两个值,由于name被 props接收了,所以只有message属性
            console.log(this.$listeners); // sayHello: f
     }
}
</script>
复制代码
  • 孙组件
<template>
</template>
<script>
export default {
    created() {
        console.log(this.$attrs); // name,message 
        console.log(this.$listeners) // sayHello: f
        this.$emit('sayHello', 'hello')//可以触发 father 组件中的sayHello函数
    },
}
</script>
复制代码

$parent$children

子组件可以用 this.$parent 访问父组件,子组件被推入父组件的 $children 数组中。

官方提示:节制地使用 $parent$children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信,并且$children不保证顺序也不是响应式的。

  • 父组件
<template>
  <div class="father">
    <child></child>
    <button @click="name">点击改变子组件的值</button>
  </div>
</template>

<script>
import child from './child'
export default {
  components: { child },
  data() {
    return {
      msg: '父组件message'
    }
  },
  methods:{
    name(){
      this.$children[0].message = "hello"
    }
  }
}
</script>
复制代码
  • 子组件
<template>
  <div class="com_a">
    <span>{{message}}</span>
    <p>获取父组件的值:{{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data(){
    return {
      message:'子组件message'
    }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>
复制代码

EventBus

在小型的项目中使用,

1.在入口函数中初始化evnetBus,将一个新的vue实例放在原型链上作为全局的$eventBus

Vue.prototype.$eventBus = new Vue()
复制代码

2.实例化的vue自带$on$emit$off方法

his.$eventBus.$emit('nameOfEvent', { ... pass some event data ...});//创建发出的事件

this.$eventBus.$on('nameOfEvent',($event) => {
  // ...
}) // 监听事件
this.$eventBus.$off('nameOfEvent') //移除事件监听
复制代码

provide和inject

父组件通过provide以对象的形式向子组件暴露一些属性,子组件通过inject注入相应属性。

  • 祖先组件

    provide选项:一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。

<template>
    <div>
        <child prop="data"></child>
    </div>
</template>
 
<script>
export default {
    provide: {
        name: 'Tom'
    }
}
复制代码
  • 后代组件

    inject选项:一个字符串数组或一个对象,对象的 key 是本地的绑定名,value是注入内容的key

<template>
    <div>
        {{name}}
    </div>
</template>
 
<script>
export default {
    name: 'child',
    inject: [name]
}
</script>
复制代码

注意的点

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

Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。vue组件中的状态就是可响应的,直接在根组件中将组件本身注入 provide,就可以在后代组件中任意访问根组件中的所有状态,根组件就成为了全局状态的容器

provideinject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

原因:provide/inject 中变量的修改是无法控制的,并且它破坏了vue的单向数据流,多个后代依赖了同一个祖先的状态,只要一个后代对状态进行了改变,则其他的组件都会受到影响,所以最好不要使用 provide/inject 做全局状态管理,而是使用可以追踪回溯的vuex。

vuex

Vuex是一个状态管理的插件,可以解决不同组件之间的数据共享和数据持久化,适用于大型项目。

  • State:存储状态数据
  • Getter:从状态数据派生数据,相当于State的计算属性。
  • Mutation:存储用于同步更改状态数据的方法,默认传入的参数为state。
  • Action:存储用于异步更改状态数据,但不是直接更改,而是通过触发Mutation方法实现,默认参数为context。
  • Module:Vuex模块化。

vuex知识点较多,日后再学习总结~

总结

  • 父子间通信最常用的就是props/$emit和v-model
  • 如果是嵌套组件通信,开发组件库可以使用provide/inject,中小型项目可以使用EventBus
  • 大型项目中使用vuex来进行兄弟之间和跨级之间通信

参考博客

聊聊 Vue 中 provide/inject 的应用

文章分类
前端
文章标签