props 父传子和 $emit 子传父
该应用场景多用于父子组件之间的通信。
注意⚠️:
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
步骤
父传子
- 在父组件内,引入子组件并挂载
- 在父组件挂载的子组件标签上,通过绑定属性
v-bind
(缩写::
)的方式,将数据传递给该子组件 - 在子组件通过
props
来接收父组件传递的数据
子传父
- 在子组件内,将(改变后的)数据通过
this.$emit
触发当前实例上的事件,附加参数都会传给监听器回调 - 在父组件挂载的子组件标签上,通过绑定事件监听器
v-on
(缩写:@
)的方式,监听this.$emit
触发的事件,在methods
中定义监听器回调方法来获取数据
实例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>场景一:父子组件通信</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body> <div id="app"> <!-- 父组件挂载到 #app 元素下 --> <m-self></m-self> </div></body><script> // 声明子组件 Vue.component('m-son', { // 子组件接收父组件通过绑定属性(v-bind/:)绑定的属性 // 注意⚠️:一般 props 接收的数据作为初始化数据 props: { b: { type: String, default: '' } }, // :value="b",而不用 v-model="b" 是因为 vue 中 prop 的使用,造成其父子 prop 之间形成了一个单向下行绑定,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。 template: `<div> <input type="text" :value="b" @input="onInput" /> </div>`, methods: { onInput(e) { let data = e.target.value // 子组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。 this.$emit('onChangeB', data) } } }) // 声明父组件,并将父组件挂载到 #app 上 let mSelf = new Vue({ el: '#app', data() { return { a: '将要传递给子组件的数据' } }, // 父组件通过 v-bind(/:) 的方式,传递数据到子组件 // 通过 v-on(/@) 的方式,监听子组件中 $emit 触发的事件 template: `<div> <p>{{a}}</p> <m-son :b="a" @onChangeB="onChange"></m-son> </div>`, methods: { onChange(data) { this.a = data } } })</script></html>
$parent
子访问/绑定父 和 $children
父访问/绑定子
$parent
,父实例,如果当前实例有的话。$parent
,当前实例的直接子组件。需要注意$children
并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用$children
来进行数据绑定,考虑使用一个数组配合v-for
来生成子组件,并且使用 Array 作为真正的来源。
注意⚠️:
节制地使用 $parent
和 $children
- 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信。
步骤
$parent
子访问/绑定父
- 在子组件内,通过
this.$parent
获取父组件的实例(如果有父组件的话),实例上面当然可以获取到其挂载的数据 - 在子组件内,通过
v-model
绑定父组件的数据,利用数据响应式完成子传父
$children
父访问/绑定子
- 在子组件内,通过
this.$parent
拿到父组件的数据,并赋值给子组件,完成数据初始化 - 在子组件内,通过
v-model
绑定子组件中的数据 - 在父组件内,
mounted
挂载后,通过this.$children
获取到子组件实例数组,将某一子组件实例赋值给父组件的某个变量,利用对象的特性,实现数据响应式。
实例
$parent
子访问/绑定父
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>场景二:父子组件通信</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body> <div id="app"> <!-- 父组件挂载到 #app 元素下 --> <m-self></m-self> </div></body><script> // 声明子组件 Vue.component('m-son', { // 子组件中,通过 this.$parent 访问父组件实例 template: `<div> <input type="text" v-model="$parent.a" /> </div>` }) // 声明父组件,并将父组件挂载到 #app 上 let mSelf = new Vue({ el: '#app', data() { return { a: '将要传递给子组件的数据' } }, template: `<div> <p>{{a}}</p> <m-son></m-son> </div>` })</script></html>
$children
父访问/绑定子
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>场景二:父子组件通信</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body> <div id="app"> <!-- 父组件挂载到 #app 元素下 --> <m-self></m-self> </div></body><script> // 声明子组件 Vue.component('m-son', { data() { return { // 子组件中,通过 this.$parent 获取父组件实例,完成数据初始化 b: this.$parent.a } }, template: `<div> <input type="text" v-model="b" /> </div>` }) // 声明父组件,并将父组件挂载到 #app 上 let mSelf = new Vue({ el: '#app', data() { return { a: '将要传递给子组件的数据', children: null } }, template: `<div> <p>{{children && children.b}}</p> <m-son></m-son> </div>`, mounted() { // 实例挂载后,将子组件实例赋值给父组件中定义的数据 // 注意⚠️:$children 在挂载后才会取到值,而且不是响应式的。为了达到响应式的效果,这里采用重新赋值,利用了对象的特性 this.children = this.$children && this.$children[0] } })</script></html>
$root
请参考 $parent
$root
当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
v-bind="$attrs"
祖传孙和 v-on="$listeners"
孙传子
$attr
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和style
除外),并且可以通过v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。$listeners
包含了父作用域中的 (不含.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
该应用场景可用于更高层次组件之间的通信。
注意⚠️:
v-bind="$attrs"
和 v-bind="$attrs"
的写法,不能采用简写。
步骤
祖传孙
- 在祖组件内,通过绑定属性
v-bind
(简写::
) 的方式来传递数据 - 在各级子组件内,绑定
$attrs
,即v-bind="$attrs"
,获取父级的绑定属性 - 在最后的孙组件内,通过
$attrs
去获取祖组件传递的数据
孙传祖
- 在祖组件内,通过绑定事件
v-on
(简写:@) 的方式来接收孙组件回调数据 - 在各级子组件内,绑定
$listeners
即v-on="$listeners"
,获取父级的事件监听器 - 在最后的孙组件内,通过
this.$listeners
获取祖组件的事件监听器,在数据改变后,执行该监听器传递数据。或者采用$emit
去触发
实例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>场景三:父子孙子等更高级别组件通信</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body> <div id="app"> <!-- 父组件挂载到 #app 元素下 --> <m-self></m-self> </div></body><script> // 声明曾孙组件 Vue.component('m-great-grandson', { // 孙组件通过 $attrs 获取祖组件传递的数据 template: `<div> <input type="text" :value="$attrs.b" @input="onInput" /> </div>`, methods: { onInput(e) { let data = e.target.value // 孙组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。 // this.$emit('onChangeB', data) // 或者通过 $listeners 获取事件监听器,执行监听器 this.$listeners.onChangeB(data) } } }) // 声明孙组件 Vue.component('m-grandson', { // 每层子组件都绑定 $attrs (v-bind="$attrs"),将数据传给子组件 // 每层子组件都绑定 $listeners (v-on="$listeners"),将事件监听器传给子组件 template: `<div> <m-great-grandson v-bind="$attrs" v-on="$listeners"></m-great-grandson> </div>` }) // 声明子组件 Vue.component('m-son', { // 每层子组件都绑定 $attrs (v-bind="$attrs"),将数据传给子组件 // 每层子组件都绑定 $listeners (v-on="$listeners"),将事件监听器传给子组件 template: `<div> <m-grandson v-bind="$attrs" v-on="$listeners"></m-grandson> </div>` }) // 声明父组件,并将父组件挂载到 #app 上 let mSelf = new Vue({ el: '#app', data() { return { a: '将要传递给曾孙组件的数据' } }, // 父组件内,通过 v-bind(/:) 绑定属性 (:b="a")。每层子组件都绑定 $attrs (v-bind="$attrs"),实现数据的祖传孙 // 父组件内,通过 v-on(/@) 绑定事件 (@onChangeB="onChange")。每层子组件都绑定 $listeners (v-on="$listeners"),实现事件监听器的祖传孙 template: `<div> <p>{{a}}</p> <m-son :b="a" @onChangeB="onChange"></m-son> </div>`, methods: { onChange(data) { this.a = data } } })</script></html>
eventbus 中央事件管理
通过一个公共的区域,来管理数据。可用于非父子组件通信。
步骤
- 声明两个子组件 a 组件和 b 组件,在同一父组件中引入
- 声明 bus 中央事件管理,
let bus = new Vue()
- a 组件传值到 b 组件
- 在 a 组件内,创建后,将数据赋值给
bus
,同时开启事件监听器。触发事件后,回调处理参数 - 在 b 组件内,创建后,将
bus
数据赋值给 b 组件,初始化数据。在数据改变后通过bus.$emit
触发事件监听器,回调数据,完成数据传递
实例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>场景四:非父子组件通信</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body> <div id="app"> <!-- 父组件挂载到 #app 元素下 --> <m-self></m-self> </div></body><script> // 中央事件管理 let bus = new Vue() // 声明子组件 a Vue.component('m-son-a', { data() { return { a: '传给组件 b 数据' } }, template: `<div> <p>{{a}}</p> </div>`, created() { // 将组件 a 数据赋值给 bus,并开启事件监听器。触发后,回调数据即为 组件 b 传递 bus.a = this.a bus.$on('onChangeA', (data) => { this.a = data }) }, methods: { } }) // 声明子组件 b Vue.component('m-son-b', { data() { return { b: '' } }, template: `<div> <input type="text" :value="b" @input="onInput" /> </div>`, created() { // 数据初始化 this.b = bus.a }, methods: { onInput(e) { let data = e.target.value // 组件 b 中更改了数据,通过 $emit 触发自定义事件。附加参数都会传给监听器回调。 bus.$emit('onChangeA', data) } } }) // 声明父组件,并将父组件挂载到 #app 上 let mSelf = new Vue({ el: '#app', template: `<div> <m-son-a></m-son-a> <m-son-b></m-son-b> </div>` })</script></html>