组件之间存在父子关系、兄弟关系、隔代关系,针对不同的场景,选择更适合自己的通讯方式。
方法一、props/$emit
父子组件的数据通讯
- props主要是子组件接收父组件传递的数据
$emit
是子组件触发父组件传递的方法,通过参数传递给父组件数据
父组件Parent.vue
<template>
<div class="parent">
<Child :obj="obj" @my-event="myEventHandle"></Child>
</div>
</template>
<script>
import Child from "./Child.vue"
export default {
data () {
return {
obj: {
a: 1,
b: 2
}
}
},
components: {
Child
},
methods: {
myEventHandle (data) {
console.log(data)
}
}
}
</script>
复制代码
子组件Child.vue
<template>
<div class="child">
<button @click="$emit('my-event', '子组件传给父组件的数据')">click me</button>
</div>
</template>
<script>
export default {
props: {
obj: {
type: Object,
default: () => ({}) // {}
// default: function () {
// return {}
// } // {}
// default: () => {} // undefined
}
}
}
</script>
复制代码
PS:Props接收对象时,默认值这么写
default: () => {}
,在不传obj的情况下obj为undefined,使用上面2种方式均可
.sync修饰符
模拟组件之间双向数据流的简写形式,推荐update:myPropName
的模式触发事件。
父组件
<template>
<div class="about">
<div>.sync 修饰符</div>
<!-- <SyncDemo :title="title" @update:title="title = $event"></SyncDemo> -->
<SyncDemo :title.sync="title"></SyncDemo>
</div>
</template>
<script>
import SyncDemo from "./SyncDemo.vue"
export default {
data () {
return {
title: "sync demo"
}
},
components: {
SyncDemo
}
}
</script>
复制代码
子组件
<template>
<div>
<div>{{title}}</div>
<button @click="$emit('update:title', 'change title')">change title</button>
</div>
</template>
<script>
export default {
props: ["title"]
}
</script>
复制代码
注意带有 .sync
修饰符的 v-bind
不能和表达式一起使用,可以同时蚌寺那个多个属性
v-model
组件上的 v-model
默认会利用名为 value
的 prop
和名为 input
的事件
父组件
<template>
<div class="about">
<div>v-model component 自定义输入组件 {{ cval }}</div>
<!-- <VmodelComponent :value="cval" @input="cval = $event"></VmodelComponent> -->
<VmodelComponent v-model="cval"></VmodelComponent>
</div>
</template>
<script>
import VmodelComponent from "./VmodelComponent.vue"
export default {
data () {
return {
val: ""
}
},
components: {
VmodelComponent
}
}
</script>
复制代码
子组件
<template>
<div class="about">
<input :value="value" @input="$emit('input', $event.target.value)"/>
</div>
</template>
<script>
export default {
props: ["value"]
}
</script>
复制代码
方法二、eventBus
通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
全局注册
main.js
// 定义事件车
const bus = new Vue()
Vue.prototype.$bus = bus
复制代码
局部注册
eventBus.js
import Vue from 'vue'
export default const bus = new Vue()
复制代码
实现
触发事件eventHandle并附带参数
this.$bus.$emit("eventHandle", "兄弟组件传递数据")
监听当前实例上的自定义事件eventHandle,通过回调接收附带参数
this.$bus.$on("eventHandle", function (data) {
console.log(data)
})
beforeDestory中移除自定义事件监听器
this.$bus.$off("eventHandle")
复制代码
方法三、Vuex
参考Vuex官网
方法四、provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
祖先组件注入依赖provideVal,当点击按钮修改data中数据val时,子孙组件并没有实时更新。
祖先组件 Provide.vue
<template>
<div>
{{val}}
<button @click="changeHandle">change val</button>
<Inject />
</div>
</template>
<script>
import Inject from './Inject.vue'
export default {
data () {
return {
val: "父组件的数据val"
}
},
provide () {
return {
provideVal: this.val
}
},
methods: {
changeHandle () {
this.val = "更改父组件的数据val"
}
},
components: {
Inject
}
}
</script>
复制代码
子孙组件 Inject.vue
<template>
<div>
<div>接收注入依赖: {{provideVal}}</div>
</div>
</template>
<script>
export default {
inject: ["provideVal"]
}
</script>
复制代码
官网提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象(通过函数注入),那么其对象的 property 还是可响应的。
祖先组件 Provide.vue
<template>
<div>
{{val}}
<button @click="changeHandle">change val</button>
<Inject />
</div>
</template>
<script>
import Inject from './Inject.vue'
export default {
data () {
return {
val: "父组件的数据val"
}
},
provide () {
return {
provideVal: () => this.val
}
},
methods: {
changeHandle () {
this.val = "更改父组件的数据val"
}
},
components: {
Inject
}
}
</script>
复制代码
子孙组件 Inject.vue
<template>
<div>
<div>接收注入依赖: {{provideVal()}}</div>
<div>计算属性,不需要执行方法了 {{provideValNew}}</div>
</div>
</template>
<script>
export default {
inject: {
provideVal: {
default: () => ({})
}
},
computed: {
provideValNew () {
return this.provideVal()
}
}
}
</script>
复制代码
inject
inject是对象时,value有两个属性
- from 注入依赖中搜索来源的key
- default 降级后的默认值
方法五、$children
/$parent
$parent
/$children
访问父/子组件的实例
子组件可以存在多个,this.$children
为数组类型
孙子组件可以通过this.$parent.$emit('my-event')
触发父组件传递给子组件的方法
当组件层级更深时,推荐使用provide/inject
父组件 Parent.vue
<template>
<div class="parent">
<Child @my-event="myEventHandle"></Child>
</div>
</template>
<script>
import Child from "./Child.vue"
export default {
data () {
return {
val: "我是父组件数据!"
}
},
mounted () {
console.log(this.$children[0].val)
console.log(this.$children[0].$children[0].val)
},
methods: {
myEventHandle () {
console.log("myEventHandle")
}
},
components: {
Child
}
}
</script>
复制代码
子组件 Child.vue
<template>
<div class="child">
<Grandson />
</div>
</template>
<script>
import Grandson from "./Grandson.vue"
export default {
data () {
return {
val: "我是子组件数据"
}
},
mounted () {
console.log(this.$parent.val)
},
components: {
Grandson
}
}
</script>
复制代码
孙子组件 Grandson.vue
<template>
<div class="grandson">
<button @click="$parent.$emit('my-event')">click me</button>
</div>
</template>
<script>
export default {
data () {
return {
val: "我是孙子组件数据"
}
}
}
</script>
复制代码
$dispatch
实现向上通知某个子组件触发其父组件的方法
Vue.prototype.$dispatch = function (eventName, componentName, ...args) {
let parent = this.$parent
while (parent) {
// 只有是特定组件,才会触发事件。而不会一直往上,一直触发
const isSpecialComponent = parent.$options.name === componentName
if (isSpecialComponent) {
// 触发了,就终止循环
parent.$emit(eventName, ...args)
return
}
parent = parent.$parent
}
}
复制代码
$broadcast
向下通知某个子组件触发其父组件的方法
Vue.prototype.$broadcast = function (eventName, componentName, ...args) {
// 这里children是所有子组件,是子组件不是后代组件哈
const children = this.$children
broadcast(children)
// 这里注意,抽离新的方法递归,而不是递归$broadcast
function broadcast (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
const isSpecialComponent = child.$options.name === componentName
if (isSpecialComponent) {
// 触发了,就终止循环
child.$emit(eventName, ...args)
return
}
// 没触发的话,就看下有没有子组件,接着递归
child.$children.length && child.$broadcast(eventName, componentName, ...args)
}
}
}
复制代码
参考来源:颜酱
方法六、ref
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
父组件通过ref触发子组件的输入框聚焦:
父组件 ParentRefDemo.vue
<template>
<div>
<ChildRefDemo ref="childRefs" />
</div>
</template>
<script>
import ChildRefDemo from "./ChildRefDemo.vue"
export default {
mounted () {
// this.$refs.childRefs获取子组件实例
this.$refs.childRefs.focusHandle()
},
components: {
ChildRefDemo
}
}
</script>
复制代码
子组件 ChildRefDemo.vue
<template>
<div>
<input ref="inputRefs" />
</div>
</template>
<script>
export default {
methods: {
focusHandle () {
this.$refs.inputRefs.focus()
}
}
}
</script>
复制代码
$refs只会在组件渲染完之后生效,并且他不是响应式的,应该避免在模板和计算属性中使用
方法七、$attrs
/$listeners
$attrs
父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外),默认绑定在子组件的根元素上。那怎么绑定到子组件非根元素上呢?
inheritAttrs: false
阻止默认行为,但不影响 class 和 style 绑定;同时需要v-bind="$attrs"
传入内部组件
父组件
<template>
<div>
<Child type="checkbox" />
</div>
</template>
<script>
import Child from "./Child.vue"
export default {
components: {
Child
}
}
</script>
复制代码
子组件
<template>
<div>
<input v-bind="$attrs" />
</div>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
复制代码
$listeners
组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符.native,此时组件内根元素应该是input,一旦根元素有所更改,父级的.native 监听器将静默失败。那怎么才能将原生事件绑定在子元素内呢?
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners"
传入内部组件
父组件
<template>
<div class="about">
<VmodelEvent @focus="myFocus"></VmodelEvent>
</div>
</template>
<script>
import VmodelEvent from "./VmodelEvent.vue"
export default {
methods: {
myFocus () {
console.log("focus")
}
},
components: {
VmodelEvent
}
}
</script>
复制代码
子组件
<template>
<div>
<input v-on="$listeners" />
<Grandson v-on="$listeners" />
</div>
</template>
<script>
import Grandson from "./Grandson.vue"
export default {
components: {
Grandson
}
}
</script>
复制代码
在创建更高层次的组件时非常有用,
$attrs
/$listeners
层层传递
方法八、Vue.observable( object )
让object可响应,Vue内部就是通过这个方法处理data中的数据。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。
计算属性
利用计算属性进行响应式通讯
<template>
<div>
<div>{{msg}}</div>
<button @click="change">change msg</button>
</div>
</template>
<script>
import Vue from 'vue'
const state = Vue.observable({ msg: "Hello Observable" })
export default {
computed: {
msg () {
return state.msg
}
},
methods: {
change () {
state.msg = "change msg"
}
}
}
</script>
复制代码
渲染函数
利用渲染函数进行响应式通讯
<script>
import Vue from 'vue'
const state = Vue.observable({ msg: "Hello Observable" })
export default {
render (h) {
return h('button', {
on: { click: () => { state.msg = 'change msg' } }
}, `count is: ${state.msg}`)
}
}
</script>
复制代码
方法九、$root
所有的子组件都可以通过this.$root
来访问根实例的数据、计算属性和方法,还可以更改根实例的数据。对于demo或者小型的具有少量组件的应用还是比较方便的,对于中大型项目建议使用Vuex来管理应用的状态。这个实例可以作为一个全局 store 来访问或使用。
new Vue({
data () {
return {
rootVal: "$root获取根实例的值、计算属性和方法"
}
},
computed: {},
methods: {},
render: h => h(App)
}).$mount('#app')
复制代码
方法十、其他方式
插槽传值、路由传参、本地存储