likes
comments
collection
share

总结下vue组件通信的几种方式

作者站长头像
站长
· 阅读数 35

前言

这几天在写公司的业务,然后涉及到了组件之间的通信,通过这篇文章总结下vue组件通信的几种方式,做个记录。方便后续查阅。(在面试中出现的频率也比较高)

方式

自定义事件($emit)

我们遇到最多的就是父子间的通信,父组件通过props传值给子组件,然后子组件想修改props的值,但是vue是单向数据流,数据只能由父组件单向流向子组件。 不能由子组件修改父组件的数据,这会让数据难以维护,追踪。

如果子组件要修改,需要通知父组件去修改,这样父组件修改好后再传给子组件,保证了单向数据流。

主要是通过自定义事件来实现。

父组件把自定义事件传给子组件,然后子组件需要修改的时候通过$emit触发自定义事件,然后把参数传入,父组件收到参数会对数据进行处理。

父组件

// father.vue
<template>
  <div id="app">
    <child :name="name" @setName="setName"></child>
  </div>
</template>

<script>
import child from "@/components/child.vue"
export default {
  components: {
    child
  },
  data () {
    return {
      name: '答案'
    }
  },
  methods: {
    setName (name) {
      this.name = name
    }
  }
}
</script>

子组件

//child.vue
<template>
  <div>
    {{name}}
    <button @click="changeName">change</button>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      name: String
    }
  },
  methods: {
    changeName () {
      this.$emit('setName', 'cp3')
    }
  }
}

一开始name是答案

点击按钮,name就变成cp3了。这样达到了子组件向父组件传值的效果。

如果你直接在changeName把name修改了,那么就会报错

总结下vue组件通信的几种方式

当然,这个报错只针对赋值的情况,如果你的props是对象,修改对象的属性,不会报错,但是最好不要这样做。

$parent/$children

除了上面的自定义事件来实现父子组件通信,还可以通过 $parent/$children访问到父子组件的实例,进而实现组件通信。

从字面上可以看出,$parent是访问父组件的实例,$children是访问子组件的实例。

父组件:

// father.vue
{
  methods: {
    setName (name) {
      this.name = name
    }
  }
}

// child.vue
{
  props: {
    name: {
     type: String
    }
  }
  methods: {
    setParentName (name) {
      this.$parent.setName(name)
    }
  }
}

可以看到,当子组件调用setParentName,会调用this.$parent.setName(name),先获取父组件实例,然后再获取到setName方法,达到调用到父组件的方法,和父组件通信。

$children也是类似。这里不再赘述。

$parent/$children适合一些逻辑简单些的父子组件通信。

如果组件逻辑复杂的,后期就不好维护,因为你有可能不知道为什么数据就变了,难以追溯。

而且如果组件的关系变了,不是父子关系了,就得把用到$parent/$children的地方都要改。

$refs

除了 $parent/$children , 还可以通过 $refs 来访问组件的实例。

如果ref定义在普通的元素上,就是对应的dom元素。

如果定义在组件上,就是对应的组件实例。

// father.vue
<child ref="child"></child>

console.log(this.$refs.child) // 子组件的实例
console.log(this.$refs.child.name) // 实例的数据
console.log(this.$refs.child.getName()) // 实例的方法

这个$refs是单向的,访问子组件,或者子组件的子组件等等,并不能往上访问父组件。

$refs一般用来访问子组件就好了,跨多级访问写起来麻烦,可读性也不高。

事件总线(eventBus)

上面的自定义事件和parent/parent/parent/children是针对父子组件的,如果遇到兄弟组件,那么该怎么做?

可以使用事件总线(eventBus)。

新建个event.js文件,在里面新建个vue实例。

export default new Vue()

然后在需要相应的组件,导入该文件

监听的组件:

import event from './event.js'
event.$on('setName', (name) => {
    this.name = name
})

触发的组件:

import event from './event.js'
event.$emit('setName', 'cp3')

这个思路也是自定义事件,$on监听,$emit触发,但是这个灵活性高一些,在对应的组件引入即可,可实现全局事件监听,适合非父子组件的监听。

vuex

事件总线适合一些非父子之间的简单通信。

如果你的页面很复杂的话,还是推荐vuex

vuex,全局管理组件的状态,我们可以把组件公共的数据定义在vuex上,如果修改公共的数据,调用vuex的相应方法,修改后的值会同步到相应的组件上。适合多组件的复杂通信。

vuex的几个概念:

  • state

    定义公共数据的地方

  • getter

    类似于计算属性,可以基于state的数据进行再次计算。

  • mutation

    同步修改state

  • action

    支持异步,通过调用mutation去修改state

  • module

    模块,每个模块可以有自己的state,getter,mutation,action。如果组件过多,复杂,建议组件都有自己的模块,方便管理。

  {
    state: {
      name: '答案cp3'
    },
    getters: {
      fullName (state) {
        return state.name
      }
    },
    mutations: {
      setName (state, params) {
        state.name = params
      }
    },
    actions: {
      asyncSetName ({ commit }, params) {
        // 模拟异步
        setTimeout(() => {
          commit('setName', params)
        }, 200)
      }
    },
    modules: {
      moduleName // 使用时改成实际引入的名字
    }
  }

举个例子:

如果有3个组件需要state的name,则只需要setName执行一次,state的name更新,3个组件的name也会更新。

依赖注入(provide/inject)

这个比较少用,适合嵌套很深的组件。在父组件通过provide定义数据,然后在子孙组件(不管多深)都能通过inject拿到provide定义的数据。

比props一层一层传好一点,它只需定义一次,子孙组件都能拿到。

// father
{
 provide: {
   obj: {name: '答案cp3'}
 }
}

// son or grandson..
{
    inject: ['obj'],
    mounted() {
     console.log(this.obj) // {name: '答案cp3'}
    }
}

provide/inject并不是响应式的,所以大家用的时候需要注意⚠️

$attrs/$listerner

实话说,这两个属性我用的不多😂,我今天才看文档学的。所以我就简单讲2句,就2句。

我们传给组件的属性,如果组件没有通过props接收所有属性,那么没有接收到的属性就会被绑定到根元素上。

如果我们不要绑定到根元素的行为,我们可以通过设置inheritAttrsfalse来阻止继承。

我们可以通过this.$attrs可以拿到这些属性。

然后还可以通过v-bind="$attrs"继续传给子孙组件。

// father.vue
<child name="答案cp3" :isBoy="true"></child>

// child.vue
<grandson v-bind="$attrs"></grandson>
{
 inheritAttrs: false,
 props: {
   name: String
 },
 mounted () {
   console.log(this.$attrs) // {isBoy: true}   除了props的其它属性都在这里
 }
}

$listerner是把当前在组件上定义的事件都包裹在里面(不包含.native修饰的事件),然后在子组件通过$listerner访问到父组件的方法,可以调用。

子组件可以通过v-on="$listerner"继续传给下一代。

// father.vue
<child @input="inputEvent" :customEvent="customEvent"></child>

// child.vue
<grandson v-on="$listerner"></grandson>
{
 props: {
   name: String
 },
 mounted () {
   console.log(this.$listerner) // {input: inputEvent, customEvent: customEvent}
   this.$listerner.customEvent() // 执行父组件的方法
 }
}

总结

总结这篇文章讲到的vue组件通信的方式

  • 自定义事件($emit)
  • $parent/$children
  • $refs
  • 事件总线(eventBus)
  • vuex
  • 依赖注入(provide/inject)
  • $attrs/$listerner
转载自:https://juejin.cn/post/7083140749046317093
评论
请登录