乐趣区

关于vue.js:vue组件通信方式有哪些

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 监听并接管参数。通过一个例子,阐明子组件如何向父组件传递数据。在上个例子的根底上, 点击页面渲染进去的 ariticleitem, 父组件中显示在数组中的下标

// 父组件中
<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(接管到子组件传入的值){// 后续逻辑}         

参考 前端 vue 面试题具体解答

二、$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] 失去的就是 c hild1

总结

下面两种形式用于父子组件之间的通信,而应用 props 进行父子组件通信更加广泛; 二者皆不能用于非父子组件之间的通信。

三、provide/ inject

概念:

provide/ injectvue2.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.vue

export 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.js

import 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. 发送事件

假如你有两个组件: additionNumshowNum, 这两个组件能够是兄弟组件也能够是父子组件;这里咱们以兄弟组件为例:

<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 各个模块
  1. state:用于数据的存储,是 store 中的惟一数据源
  2. getters:如 vue 中的计算属性一样,基于 state 数据的二次包装,罕用于数据的筛选和多个数据的相关性计算
  3. mutations:相似函数,扭转 state 数据的惟一路径,且不能用于解决异步事件
  4. actions:相似于 mutation,用于提交mutation 来扭转状态,而不间接变更状态,能够蕴含任意异步操作
  5. 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>
退出移动版