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
和 $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
v-bind="$attrs"
祖传孙和 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>
对象,共用同一对象
注意⚠️:
共用同一对象,可能会引起一些数据的混乱。不理解的情况下会造成不是预期的变化。
步骤
- 声明一个公用对象
- 在涉及的组件内引入该对象,并在组件创建之后初始化数据
- 绑定该数据到 view 视图
实例
<!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>
// 声明一个公用对象,利用对象的特性完成数据共享。也可以通过 es6 模块 导出导入对象。index.js 中 export const data = { a: ''} ,import { data } from './index.js'
// 将对象赋值给某个变量,这个变量修改了对象中的属性,则原对象也会改变(对象是地址引用,引用类型)
let data = {
a: '传给组件 b 数据'
}
// 声明子组件 a
Vue.component('m-son-a', {
data() {
return {
data: {}
}
},
template: `<div>
<p>{{data && data.a}}</p>
</div>`,
created() {
// 初始化数据
this.data = data
}
})
// 声明子组件 b
Vue.component('m-son-b', {
data() {
return {
data: {}
}
},
template: `<div>
<input type="text" v-model="data.a" />
</div>`,
created() {
// 数据初始化
this.data = 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>
provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
实例
<!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', {
// inject 引入 provide 注入的属性
inject: ['a', 'onChangeB'],
// 孙组件通过 inject 获取祖组件传递的数据
template: `<div>
<input type="text" :value="a" @input="onInput" />
</div>`,
methods: {
onInput(e) {
let data = e.target.value
// 孙组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。
// this.$emit('onChangeB', data)
// 或者
this.onChangeB(data)
}
}
})
// 声明孙组件
Vue.component('m-grandson', {
template: `<div>
<m-great-grandson></m-great-grandson>
</div>`
})
// 声明子组件
Vue.component('m-son', {
template: `<div>
<m-grandson></m-grandson>
</div>`
})
// 声明父组件,并将父组件挂载到 #app 上
let mSelf = new Vue({
el: '#app',
data() {
return {
b: '将要传递给曾孙组件的数据'
}
},
// provide 可以是对象,也可以是返回一个对象函数,该对象包含可注入的属性
provide() {
return {
a: this.b,
onChangeB: this.onChange
}
},
// 父组件内,通过 provide 声明注入属性
// 父组件内,通过 v-on(/@) 绑定事件 (@onChangeB="onChange")
template: `<div>
<p>{{b}}</p>
<m-son @onChangeB="onChange" ></m-son>
</div>`,
methods: {
onChange(data) {
this.b = data
}
}
})
</script>
</html>