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>