一、父子组件通信
父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信
1.父组件向子组件传值(props)
props的特点:
- props只能是父组件向子组件进行传值,props使得父子组件之间形成一个单向的下行绑定。子组件的数据会随着父组件的更新而响应式更新。
- props可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以是传递一个函数。
- props属性名规则:若在props中使用驼峰形式,模板中标签需要使用短横线的形式来书写。
用法:
- 给父组件中子组件标签,添加属性的方式,传值
- 子组件通过props进行接收
- 渲染使用
示例:
父组件
// 父组件
<template>
<div id="father">
<son :msg="msgData" :fn="myFunction"></son>
</div>
</template>
<script>
import son from "./son.vue";
export default {
name: father,
data() {
msgData: "父组件数据";
},
methods: {
myFunction() {
console.log("vue");
}
},
components: {
son
}
};
</script>
子组件:
// 子组件
<template>
<div id="son">
<p>{{msg}}</p>
<button @click="fn">按钮</button>
</div>
</template>
<script>
export default {
name: "son",
props: ["msg", "fn"]
};
</script>
2.子组件向父组件传递数据($emit)
$emit的特点:
- $emit 绑定一个自定义事件,当这个事件被执行的时候就会将参数传递给父组件,而父组件通过v-on监听并接收参数
用法:
- 通过$emit,向父组件发送消息通知
- 父组件,对消息进行监听
- 提供处理函数,提供逻辑
示例:
父组件:
// 父组件
<template>
<div class="section">
<Son :articles="articleList" @onEmitIndex="onEmitIndex"></Son>
<p>{{currentIndex}}</p>
</div>
</template>
<script>
import Son from './test/Son.vue'
export default {
name: 'comArticle',
components: { Son },
data() {
return {
currentIndex: -1,
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx
}
}
}
</script>
子组件:
//子组件
<template>
<div>
<div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
</div>
</template>
<script>
export default {
props: ['articles'],
methods: {
emitIndex(index) {
this.$emit('onEmitIndex', index) // 触发父组件的方法,并传递参数index
}
}
}
</script>
3.ref / $refs
介绍:
- ref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向该子组件实例
- this.$refs是一个对象,持有当前组件中注册过 ref特性的所有 DOM 元素和子组件实例
注意:
- $refs只有在组件渲染完成后才填充,在初始渲染的时候不能访问它们,并且它是非响应式的,因此不能用它在模板中做数据绑定
- 当ref和v-for一起使用时,获取到的引用将会是一个数组,包含循环数组源
用法:
<p ref="p">Hello</p>
<children ref="children"></children>
this.$refs.p
this.$refs.children
示例
<template>
<div>
<div ref="myDiv" v-for="(item, index) in arr" :key="index">{{item}}</div>
</div>
</template>
<script>
export default {
data() {
return {
arr: ['one', 'two', 'three', 'four']
}
},
mounted() {
console.log(this.$refs.myDiv)
},
methods: {}
}
</script>
<style lang="sass" scoped>
</style>
ref与$refs实现父子传参
子组件
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件'
}
},
methods: {
changeMsg() {
this.msg = '变身'
}
}
}
</script>
<style lang="sass" scoped></style>
父组件
<template>
<div @click="parentMethod">
<children ref="children"></children>
</div>
</template>
<script>
import children from 'components/children.vue'
export default {
components: {
children
},
data() {
return {}
},
methods: {
parentMethod() {
this.$refs.children //返回一个对象
this.$refs.children.changeMsg() // 调用children的changeMsg方法
}
}
}
</script>
<style lang="sass" scoped></style>
4. eventBus事件总线($ emit / $ on)
eventBus作用
非父子(兄弟)组件之间,进行简易消息传递
步骤
- 创建一个都能访问的事件总线 (空Vue实例
import Vue from 'vue'
// 创建事件总线 就相当于创建了一个新的vue实例
const Bus = new Vue()
// 导出Bus
export default Bus
- A组件(接受方),监听Bus的 $on事件
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
}
- B组件(发送方),触发Bus的$emit事件
Bus.$emit('sendMsg', '这是一个消息')
注意
有组件发布事件后 剩余的所有组件都可以进行监听事件
示例
eventbus.js
import Vue from 'vue'
// 创建vue实例并导出
const eventbus = new Vue()
export default eventbus
发送方兄弟组件Son.vue
<template>
<div class="son">
<button @click="send">传递数据</button>
</div>
</template>
<script>
import eventbus from '../utils/eventbus'
export default {
name: 'SonComp',
data() {
return {
msg: '你好熊二',
}
},
methods: {
send() {
// 触发事件
eventbus.$emit('useData', this.msg)
},
},
}
</script>
接收方兄弟Son2.vue
<template>
<div class="son">
我是熊二-{{ msg }}
</div>
</template>
<script>
import eventbus from '../utils/eventbus'
export default {
name: 'Son2Comp',
data() {
return {
msg: '',
}
},
created() {
// 通过eventbus绑定事件
eventbus.$on('useData', (v) => {
this.msg = v
})
},
}
</script>
父组件App.vue
<template>
<div class="wrapper">
<Son />
<Son2 />
</div>
</template>
<script>
import Son from "./components/Son.vue";
import Son2 from "./components/Son2.vue";
export default {
name: "App",
provide() {
return {
// 提供的数据是引用类型,后代接受到的是响应式
// 基本类型不是响应式
foo: this.foo,
money: this.money,
};
},
data() {
return {
foo: [
{
id: 1,
name: "大众",
},
],
money: 1000,
};
},
components: {
Son,
Son2,
},
methods: {},
};
</script>
5. 依赖注入 provide&inject
作用 :跨层级共享数据
这种方式就是vue中依赖注入,该方法用于 父子组件之间 的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方式来进行传值。就不用一层一层的传递数据了
provide和inject是vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。
- provide 钩子用来发送数据或方法。
- inject钩子用来接收数据或方法
语法:
- 父组件 provide提供数据
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
}
}
- 子/孙组件 inject获取数据
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
注意:
- provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
- 子/孙组件通过inject获取的数据,不能在自身组件内修改
6. $parent / $children
作用:
- 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)。
- 使用 $children 可以让组件访问子组件的实例,但是, $children 并不能保证顺序,并且访问的数据也不是响应式的。
语法:
子组件
<template>
<div>
<span>{{message}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Vue'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>
父组件中:
<template>
<div class="hello_world">
<div>{{msg}}</div>
<child></child>
<button @click="change">点击改变子组件值</button>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
data() {
return {
msg: 'Welcome'
}
},
methods: {
change() {
// 获取到子组件
this.$children[0].message = 'JavaScript'
}
}
}
</script>
在上面的代码中,子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。
注意:
- 通过 $parent 访问到的是上一级父组件的实例,可以使用 $root 来访问根组件的实例
- 在组件中使用$children拿到的是所有的子组件的实例,它是一个数组,并且是无序的
- 在根组件 #app 上拿 $parent 得到的是 new Vue()的实例,在这实例上再拿 $parent 得到的是undefined,而在最底层的子组件拿 $children 是个空数组
- $children 的值是数组,而 $parent是个对象
6.$attrs / $listeners
作用:
- $attrs–继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上
- $ listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 v-on=“$listeners” 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)
语法示例
A组件(App.vue)
<template>
<div id="app">
<!-- 此处监听了两个事件,可以在B组件或者C组件中直接触发 -->
<child1 :pchild1="child1" :pchild2="child2" :pchild3="child3" @method1="onMethod1" @method2="onMethod2"></child1>
</div>
</template>
<script>
import Child1 from "./Child1.vue";
export default {
data() {
return {
child1:'1',
child2: 2,
child3:{
name:'child3'
}
};
},
components: { Child1 },
methods: {
onMethod1(msg1) {
console.log(`${msg1} running`);
},
onMethod2(msg2) {
console.log(`${msg2} running`);
},
},
};
</script>
B组件(Child.vue)
<template>
<div class="child-1">
<h2>in child1</h2>
<p>props: {{ pchild1 }}</p>
<p>$attrs: {{ $attrs }}</p>
<hr/>
<!-- 通过 v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<!-- C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from "./Child2.vue";
export default {
data() {
return {
child1:'child1'
};
},
components: { Child2 },
props: {
pchild1:{
type:String
}
},
inheritAttrs: false,
mounted() {
this.$emit("method1",this.child1);
},
};
</script>
C组件(Child.vue)
<template>
<div class="child-2">
<h2>in child2:</h2>
<p>props: {{ pChild2 }}</p>
<p>$attrs: {{ $attrs }}</p>
<p>pchild3Name: {{ $attrs.pchild3.name }}</p>
<hr/>
</div>
</template>
<script>
export default {
data() {
return {
child2:'child2'
};
},
props: {
pChild2:{
type:String,
}
},
inheritAttrs: false,
mounted() {
this.$emit("method2",this.child2);
},
};
</script>