Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue组件间通信总结 #5

Open
guilixie opened this issue Feb 23, 2021 · 0 comments
Open

vue组件间通信总结 #5

guilixie opened this issue Feb 23, 2021 · 0 comments
Labels
Vue Vue

Comments

@guilixie
Copy link
Owner

guilixie commented Feb 23, 2021

在使用 vue 进行组件化开发的过程中,组件间的通信是一个避免不了的话题。其实仔细品读 vue 官方文档,对于这方面的介绍基本都是十分详细的。但是由于文档丰富繁杂,对于组件间通信的描述也是十分零散,需要仔细阅读。本文对笔者在开发实践过程中接触过的各种解决方案做个总结,希望读者可以在日常开发工作中有所帮助。

通过 Prop 向子组件传递数据

所有的 prop 都是父子组件单向下行绑定,父级 prop 的更新会向下流动到子组件中,但反之则不行。这种“单向数据流”会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{title}}</p>
</div>
</template>
<script>
export default {
  props: ['title']
}
</script>

<!-- 父组件<template>中 -->
<child title="我很快乐"></child>

注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。

通过 $emit 向父组件传递数据

通常使用$emit 触发当前实例上的事件,可在子组件中使用它向父组件发消息。
当子组件需要改变某个 prop 时,则只需要通知父组件去改变即可。父组件监听自定义事件,通过回调的方式改变。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{title}}</p>
  <button @click.native="$emit('changeTitle','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
  props: ['title']
}
</script>

<!-- 父组件中 -->
<template>
<child :title="title" @changeTitle="changeTitle"></child>
</template>
<script>
import Child from './child'
export default {
  components: { Child },
  data () {
    return {
      title: '我很快乐'
    }
  },
  methods () {
    changeTitle (title) {
      this.title = title
    }
  }
}
</script>

.sync

.sync 主要目的就是同步父子组件的数据,是一种语法糖的写法。实际上就是可在子组件中派发一个自定义事件update:(绑定.sync属性的名字)去改变属性。
当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{title}}</p>
  <button @click="$emit('update:title','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
  props: ['title']
}
</script>

<!-- 父组件中 -->
<template>
<child :title.sync="title"></child>
</template>
<script>
import Child from './child'
export default {
  components: { Child },
  data () {
    return {
      title: '我很快乐'
    }
  }
}
</script>

v-model

这种方式默认会利用名为 value 的 prop 和名为 input 的事件,也可以通过model选项来更改。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{value}}</p>
  <button @click="$emit('input','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
  props: ['value'],
  // 默认是value和input事件,可自定义
  // model: {
  //   prop: 'title',
  //   event: 'changeTitle'
  // }
}
</script>

<!-- 父组件中 -->
<template>
<child v-model="title"></child>
</template>
<script>
import Child from './child'
export default {
  components: { Child },
  data () {
    return {
      title: '我很快乐'
    }
  }
}
</script>

注意: .sync和v-model的方式都不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名。

$attrs 和 $listeners

  • $attrs包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

  • $listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

$root, $parent, $children

有时候可以利用组件间的父子关系去获取上面的属性和方法等。

  • $root是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

  • $parent父实例,如果当前实例有的话。

  • $children当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。

ref 和 $refs

可以在原生标签上或组件上设置ref,这样可以通过this.$refs[ref的值]获取到组件的实例。如果是原生标签上的ref直接获取到页面元素,组件的化获取到的是组件实例。获取到组件实例当然就可以获取上面的属性和方法等,也不失为一种和子组件的通信方式。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{title}}</p>
  <button @click="$emit('update:title','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
  props: ['title']
}
</script>

<!-- 父组件中 -->
<template>
<div ref="parent">
  <child ref="child" :title.sync="title"></child>
</div>
</template>
<script>
import Child from './child'
export default {
  components: { Child },
  data () {
    return {
      title: '我很快乐'
    }
  },
  mounted(){
    console.log(this.$refs)
  }
}
</script>

依赖注入 provide / inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

// 在父级中注入数据
provide() {
  return { parentMsg: "爸爸" };
},

// 在任意子组件中可以注入父级数据
inject: ["parentMsg"] // 会将数据挂载在当前实例上

中央事件总线 eventBus

首先创建一个中央事件总线,其实就是一句话搞定 export default new Vue(),导出一个vue的实例,在需要使用的组件里引入。然后在使用的地方直接import,然后在这个实例上面$emit某个自定义事件,在需要监听的地方也直接import,然后这个实例上面$on去监听执行回调。这种方式可以跨组件,兄弟组件间通信也可以。

一般是使用空的vue实例作为中央事件总线。当然也可以在实例里做一些事情,如下示例:

// eventBus进行监听,将数据直接放在Bus中
import Vue from 'vue'
const eventBus = new Vue({
  data () {
    return {
      childVal: '哈哈'
    }
  },
  created () {
    // 将$on放在eventBus中完成
    this.$on('change', value => {
      this.childVal = value
    })
  }
})
export default eventBus

然后在某个组件中去$emit事件,在另一个组件里面直接使用eventBus.childVal

// 组件1中$emit事件
import eventBus from './eventBus'
export default {
  methods: {
    changeChildVal(){
      eventBus.$emit('changeChildVal','嘻嘻')
    }
  }
}

// 组件2中直接在computed中使用eventBus.childVal
import eventBus from './eventBus'
export default {
  computed: {
    childVal () {
      return eventBus.childVal
    }
  }
}

vuex 状态管理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex

插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。

<!-- 子组件child.vue -->
<template>
<div class="container">
  <p>{{title}}<p>
  <slot :childText="childText"></slot>
</div>
</template>
<script>
export default {
  props: ['title'],
  data () {
    return {
      childText: '哈哈哈'
    }
  }
}
</script>

<!-- 父组件中 -->
<template>
  <child :title.sync="title">
    <template v-slot:default="slotProps">
      {{ slotProps }}
    </template>
  </child>
</template>
<script>
import Child from './child'
export default {
  components: { Child },
  data () {
    return {
      title: '我很快乐'
    }
  }
}
</script>

dispatch 和 broadcast

利用$parent, $children和$emit可以实现跨组件通信。实现如下:

function broadcast (componentName, eventName, params) {
  this.$children.forEach(child => {
    if(child.$option.name === componentName) {
      child.$emit.apply(child, [eventName].concat(params))
    } else {
      broadcast.apply(child, [componentName, eventName].concat(params))
    }
  })
}

export default {
  methods: {
    // 通知指定组件
    dispatch (componentName, eventName, params) {
      let parent = this.$parent
      let name = parent.$option.name
      while(parent && (!name || name !== componentName)) {
        parent = parent.$parent
        if(parent) {
          name = parent.$option.name
        }
      }

      if(parent) {
        parent.$emit.apply(parent, [eventName].concat(params))
      }
    },
    // 广播
    broadcast (componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params)
    }
  }
}

Vue.observable

让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。

router 传参

有时候跳转页面切换组件时,可以借助router进行跨组件传参,但这种只适合传递简单的数据。

参考文章

  1. vue官方文档
  2. Vue整理——组件间的通信
@guilixie guilixie added the Vue Vue label Feb 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Vue Vue
Projects
None yet
Development

No branches or pull requests

1 participant