vue组件通信形式
一、props
(父向子传值----自定义属性) / $emit(子向父传值----- 自定义事件)
父组件通过props
的形式向子组件传递数据,而通过$emit
子组件能够向父组件通信。
1. 父组件向子组件传值(props)
上面通过一个例子阐明父组件如何向子组件传递数据:在子组件article.vue
中如何获取父组件section.vue
中的数据articles:['红楼梦', '西游记','三国演义']
// section父组件<template> <div class="section"> <com-article :articles="articleList"></com-article> </div></template><script>import comArticle from './test/article.vue'export default { name: 'HelloWorld', components: { comArticle }, data() { return { articleList: ['红楼梦', '西游记', '三国演义'] } }}</script>// 子组件 article.vue<template> <div> <span v-for="(item, index) in articles" :key="index">{{item}}</span> </div></template><script>export default { props: ['articles']}</script>
留神: prop 能够从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被批改,强行批改能失效,然而控制台会有错误信息。
在子组件批改父组件传入的值的办法:
1 .sync 父组件v-on绑定自定义属性时增加修饰符.sync 在子组件中通过调用emit(′update:自定义属性′,要批改的新值)==>emit('update:自定义属性',新值) 固定写法 此时子组件中接管的值就更新成了新值(父组件中的原始值会跟着变动,控制台不会报错)
父组件中: <child :value.sync='xxx'/>
子组件中: this.$emit('update:value',yyy)
2.在子组件data中申明本人的数据,让接管的数据作为这个数据的值 ==> 子组件的数据=this.value
(这种办法理论批改的是本人的数据 父组件的数据没变)
子组件的data中: 1.接管传入的数据: props:['value']
2.newValue=this.value
3.父组件传值时传递一个援用类型,在子组件中批改援用类型的属性值并不会扭转该援用类型在堆中的地址
(这种办法会让子组件和父组件的援用类型属性的值同时更改)
子组件中: props:['value']
this.value['属性名'] = 新值 或者应用 this.$set()办法也能够
2. 子组件向父组件传值($emit,props)
$emit
绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接管参数。 通过一个例子,阐明子组件如何向父组件传递数据。 在上个例子的根底上, 点击页面渲染进去的ariticle
的item
, 父组件中显示在数组中的下标
// 父组件中<template> <div class="section"> <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article> <p>{{currentIndex}}</p> </div></template><script>import comArticle from './test/article.vue'export default { name: 'HelloWorld', components: { comArticle }, 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) } }}</script>
另外:props同样能够使子组件向父组件传值:
父组件中::在子组件标签上绑定自定义属性 这个属性的值是父组件的一个函数:1.----- <child :value='fn'></child>子组件中: 2.--------props:['value']-----接管父组件传入的函数 this.value(要传入父组件的值)------调用这个函数 把要传递的值作为形参父组件中3.-------- fn(接管到子组件传入的值){ // 后续逻辑}
二、 $children
/ $parent
下面这张图片是vue
官网的解释,通过$parent
和$children
就能够拜访组件的实例,拿到实例代表什么?代表能够拜访此组件的所有办法和data
。接下来就是怎么实现拿到指定组件的实例。
应用办法
// 父组件中<template> <div class="hello_world"> <div>{{msg}}</div> <com-a></com-a> <button @click="changeA">点击扭转子组件值</button> </div></template><script>import ComA from './test/comA.vue'export default { name: 'HelloWorld', components: { ComA }, data() { return { msg: 'Welcome' } }, methods: { changeA() { // 获取到子组件A this.$children[0].messageA = 'this is new value' } }}</script>// 子组件中<template> <div class="com_a"> <span>{{messageA}}</span> <p>获取父组件的值为: {{parentVal}}</p> </div></template><script>export default { data() { return { messageA: 'this is old' } }, computed:{ parentVal(){ return this.$parent.msg; } }}</script>
参考 前端进阶面试题具体解答
要留神边界状况,如在
#app
上拿$parent
失去的是new Vue()
的实例,在这实例上再拿$parent
失去的是undefined
,而在最底层的子组件拿$children
是个空数组。也要留神失去$parent
和$children
的值不一样,$children
的值是数组,而$parent
是个对象留神: 通过$children拿到的子组件的数组汇合 他们的下标是依据在父组件中子组件标签的书写程序来的:
<child1> <child1 />
<child2> <child2 />
child1在child2的下面书写 那么父组件中应用this.$children[0] 失去的就是child1
总结
下面两种形式用于父子组件之间的通信, 而应用props进行父子组件通信更加广泛; 二者皆不能用于非父子组件之间的通信。
三、provide
/ inject
概念:
provide
/ inject
是vue2.2.0
新增的api, 简略来说就是父组件中通过provide
来提供变量, 而后再子组件中通过inject
来注入变量。
留神: 这里不管子组件嵌套有多深, 只有调用了inject
那么就能够注入provide
中的数据,而不局限于只能从以后父组件的props属性中回去数据
举例验证
接下来就用一个例子来验证下面的形容: 假如有三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件
// A.vue<template> <div> <comB></comB> </div></template><script> import comB from '../components/test/comB.vue' export default { name: "A", provide: { for: "demo" }, components:{ comB } }</script>// B.vue<template> <div> {{demo}} <comC></comC> </div></template><script> import comC from '../components/test/comC.vue' export default { name: "B", inject: ['for'], data() { return { demo: this.for } }, components: { comC } }</script>// C.vue<template> <div> {{demo}} </div></template><script> export default { name: "C", inject: ['for'], data() { return { demo: this.for } } }</script>
四、ref
/ refs
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例,能够通过实例间接调用组件的办法或拜访数据, 咱们看一个ref
来拜访组件的例子:
// 子组件 A.vueexport default { data () { return { name: 'Vue.js' } }, methods: { sayHello () { console.log('hello') } }}// 父组件 app.vue<template> <component-a ref="comA"></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.name); // Vue.js comA.sayHello(); // hello } }</script>
五、eventBus
eventBus
又称为事件总线,在vue中能够应用它来作为沟通桥梁的概念, 就像是所有组件共用雷同的事件核心,能够向该核心注册发送事件或接管事件, 所以组件都能够告诉其余组件。
步骤:
1. 初始化
形式1:
首先须要创立一个事件总线并将其导出, 以便其余模块能够应用或者监听它.
// event-bus.jsimport Vue from 'vue'export const EventBus = new Vue()
形式2:
// main.js Vue.prototype.$bus=new Vue() // 在Vue的原型上挂载事件总线// 这种形式在应用事件总线的时候不须要在每个组件中导入bus,// 应用this.$bus.emit('自定义事件1',要传入的值)传值// 应用this.$bus.$on('自定义事件1',(val)=>{})接管 val是传入的值
2. 发送事件
假如你有两个组件: additionNum
和 showNum
, 这两个组件能够是兄弟组件也能够是父子组件;这里咱们以兄弟组件为例:
<template> <div> <show-num-com></show-num-com> <addition-num-com></addition-num-com> </div></template><script>import showNumCom from './showNum.vue'import additionNumCom from './additionNum.vue'export default { components: { showNumCom, additionNumCom }}</script>// addtionNum.vue 中发送事件<template> <div> <button @click="additionHandle">+加法器</button> </div></template><script>import {EventBus} from './event-bus.js'console.log(EventBus)export default { data(){ return{ num:1 } }, methods:{ additionHandle(){ EventBus.$emit('addition', { num:this.num++ }) } }}</script>
3. 接管事件
// showNum.vue 中接管事件<template> <div>计算和: {{count}}</div></template><script>import { EventBus } from './event-bus.js'export default { data() { return { count: 0 } }, mounted() { EventBus.$on('addition', param => { this.count = this.count + param.num; }) }}</script>
4. 移除事件监听者
如果想移除事件的监听, 能够像上面这样操作:
import { eventBus } from 'event-bus.js'EventBus.$off('addition', {})
事件总线的两个问题:
- 问题1: 为什么第一次触发的时候页面B中的on事件没有被触发
- 问题2: 为什么前面再一次顺次去触发的时候会呈现,每一次都会发现如同之前的on事件散发都没有被撤销一样,导致每一次的事件触发执行越来越多。
问题一
第一次触发的时候页面B中的on事件没有被触发
产生起因
当咱们还在页面A的时候,页面B还没生成,也就是页面B中的 created中所监听的来自于A中的事件还没有被触发。这个时候当你A中emit事件的时候,B其实是没有监听到的。
解决办法
咱们能够把A页面组件中的emit事件写在beforeDestory中去。因为这个时候,B页面组件曾经被created了,也就是咱们写的on事件曾经触发了,所以能够在beforeDestory的时候, on事件曾经触发了,所以能够在beforeDestory的时候,on事件曾经触发了,所以能够在beforeDestory的时候,emit事件
问题二
前面再一次顺次去触发的时候会呈现,每一次都会发现如同之前的on事件散发都没有被撤销一样,导致每一次的事件触发执行越来越多。
产生起因
就是说,这个$on事件是不会主动分明销毁的,须要咱们手动来销毁。(不过我不太分明这里的external bus 是什么意思,有大神能解答一下的吗,尤大大也提到如果是注册的是external bus 的时候须要革除)
解决办法
在B组件页面中增加Bus.$off来敞开
// 在B组件页面中增加以下语句,在组件beforeDestory的时候销毁。beforeDestroy () { bus.$off('get', this.myhandle)},
总结
所以,如果想要用 bus 来进行页面组件之间的数据传递,须要留神两点:
一、组件A emit 事件应在beforeDestory生命周期内。
二、组件B内的 on 记得要销毁
六、Vuex
1. Vuex介绍
Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化. Vuex 解决了多个视图依赖于同一状态
和来自不同视图的行为须要变更同一状态
的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上
2. Vuex各个模块
state
:用于数据的存储,是store中的惟一数据源getters
:如vue中的计算属性一样,基于state数据的二次包装,罕用于数据的筛选和多个数据的相关性计算mutations
:相似函数,扭转state数据的惟一路径,且不能用于解决异步事件actions
:相似于mutation
,用于提交mutation
来扭转状态,而不间接变更状态,能够蕴含任意异步操作modules
:相似于命名空间,用于我的项目中将各个模块的状态离开定义和操作,便于保护
3. Vuex实例利用
// 父组件<template> <div id="app"> <ChildA/> <ChildB/> </div></template><script> import ChildA from './components/ChildA' // 导入A组件 import ChildB from './components/ChildB' // 导入B组件 export default { name: 'App', components: {ChildA, ChildB} // 注册A、B组件 }</script>// 子组件childA<template> <div id="childA"> <h1>我是A组件</h1> <button @click="transform">点我让B组件接管到数据</button> <p>因为你点了B,所以我的信息产生了变动:{{BMessage}}</p> </div></template><script> export default { data() { return { AMessage: 'Hello,B组件,我是A组件' } }, computed: { BMessage() { // 这里存储从store里获取的B组件的数据 return this.$store.state.BMsg } }, methods: { transform() { // 触发receiveAMsg,将A组件的数据寄存到store里去 this.$store.commit('receiveAMsg', { AMsg: this.AMessage }) } } }</script>// 子组件 childB<template> <div id="childB"> <h1>我是B组件</h1> <button @click="transform">点我让A组件接管到数据</button> <p>因为你点了A,所以我的信息产生了变动:{{AMessage}}</p> </div></template><script> export default { data() { return { BMessage: 'Hello,A组件,我是B组件' } }, computed: { AMessage() { // 这里存储从store里获取的A组件的数据 return this.$store.state.AMsg } }, methods: { transform() { // 触发receiveBMsg,将B组件的数据寄存到store里去 this.$store.commit('receiveBMsg', { BMsg: this.BMessage }) } } }</script>
vuex的store,js
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const state = { // 初始化A和B组件的数据,期待获取 AMsg: '', BMsg: ''}const mutations = { receiveAMsg(state, payload) { // 将A组件的数据寄存于state state.AMsg = payload.AMsg }, receiveBMsg(state, payload) { // 将B组件的数据寄存于state state.BMsg = payload.BMsg }}export default new Vuex.Store({ state, mutations})
七、vue slot 插槽通信
父子插槽通信
能够了解为在定义组件的时候事后留好了一个插槽,父组件在调用子组件的应用将货色插到插槽外面显示,或者说从内向里读。
//父组件<div> <h3>父组件</h3> <testChild> <div>默认插槽</div> </testChild></div>//子组件 testChild<div> <h4>子组件</h4> <slot></slot> //default slot</div>
后果如下
父组件
子组件
默认插槽
父向子通信
其实就是读取父外面data的内容
/* 父组件 * list: [* {name: "aaa"}, {name: "bbb"}* ]*/<div> <h3>父组件</h3> <testChild> <template v-slot:test>//v-slot: + 插槽名 v-slot:default 也是容许的 <ul> <li v-for="item in list">{{item.name}}</li> </ul> </template> </testChild> </div>//子组件 textChild <div> <h4>子组件</h4> <slot name="test"></slot> //name="插槽名"</div>
后果如下
父组件
子组件
- aaa
- bbb
子向父通信
父组件无奈间接拜访子组件外面的变量
//父组件 <div> <h3>父组件</h3> <testChild> <template v-slot:test="data">//具名插槽,v-slot: +插槽名+ ="自定义数据名",子组件所传参数都是其属性 <ul> <li v-for="item in data.list2">{{item.name}}</li> </ul> </template> <template v-slot="dataDefalut">//默认插槽 {{dataDefalut.sName}} </template> </testChild> </div>//子组件<template> <div> <h4>子组件</h4> <slot name="test" :list2="list2"></slot> <slot :sName="name"></slot> </div></template><script> export default { name: "testChild", data(){ return { list2:[ {name:'ccc'}, {name:'ddd'} ], name:'name' } } }</script>