vue 组件之间通信的六种方式
vue 组件之间通信的六种方式
1、props 和 $emit(子 触发 父)、ref(父 调用 子)
- 父组件 给 子组件 数据传递,一般使用 props 关键字。
- 子组件 给 父组件 传递数据,一般使用 $emit 方式触发 父组件 的事件;因为 子组件 是 不能直接修改 props 中的值(即 父组件 传递的值)的,若需要修改,则要通过触发 父组件 的事件,告知父组件进行修改。
- 父组件 直接调用 子组件 的方法,可用 $refs 获取到子组件的实例,从而调用其方法。
示例代码如下:
// Parent.vue
<template>
<div>
<span>我是Parent组件</span>
<Child ref="child" :parentValue="value" @emitEdit="edit"></Child>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return {
value: "我是父组件"
}
},
methods: {
pFn() {
console.log("我是父组件的方法");
// 调用子组件的方法
this.$refs.child.cFn();
},
// 子组件触发,修改 value 的值
edit(msg) {
this.value = msg;
}
}
}
</script>
// Child.vue
<template>
<div>
<span>我是Child组件</span>
<span>父组件传的值:{{this.parentValue}}</span>
<button @click="editParentValue">修改 parentValue</button>
</div>
</template>
<script>
export default {
props: {
parentValue: String
},
methods: {
cFn() {
console.log("我是子组件的方法");
}
editParentValue() {
this.$emit("emitEdit", "子组件修改了我");
}
}
}
</script>
2、Event Bus(on、emit)
- Event Bus(事件总线)方式主要用于无父子关系的组件之间传值,比如兄弟组件之间
- 在 组件A 中使用 on注册监听事件,在组件B中使用emit 触发事件
示例代码如下:
// 新建一个vue实例,作为事件总线;可将其注册到全局或者vue的propotype上
vue.propotype.eventBus = new Vue();
// CompA.vue
<template>
<div>
<span>我是CompA组件</span>
<span>value的值:{{ value }}</span>
</div>
</template>
<script>
export default {
data() {
return {
value: "我是CompA组件"
}
},
mounted() {
this.eventBus.$on("edit", (msg) => {
this.value = msg;
})
}
}
</script>
// CompB.vue
<template>
<div>
<span>我是CompB组件</span>
<button @click="editValue">修改 CompA 的value</button>
</div>
</template>
<script>
export default {
methods: {
editValue() {
this.eventBus.$emit("edit", "CompB 修改了我");
}
}
}
</script>
疑问点:为什么不能使用 this.emit和this.on ,而要重新 new 一个空的 vue 实例?
3、provide 和 inject
- provide 和 inject 方式通常用于祖孙组件之间的通信,主要用于祖先组件向子组件传值
- provide 用于在 祖先组件 中定义可向子组件注入的属性或方法
- inject 用于在 子组件 中注入属性和方法等依赖
示例代码:
// CompA.vue
<template>
<div>
<span>我是CompA组件</span>
<span>value的值:{{ value }}</span>
<!--其中,CompA组件不一定是CompB组件的直接父级,也可以是CompB组件的祖先组件,中间可以有多层的组件引用-->
<CompB ref="compB"></CompB>
</div>
</template>
<script>
import CompB from "./CompB.vue";
export default {
components: { CompB },
data() {
return {
value: "我是CompA组件"
}
},
provide() {
return {
// 向外暴露这两个变量
aValue: this.value,
aFn: this.fn
}
},
// 使用 Vue.observable() 可使provide传递的值变成响应式,即在祖先组件中修改之后,子组件中的值也会同步变化
provide() {
this.aData = Vue.observable({
value: this.value,
aFn: this.fn
});
return {
aData: this.aData
};
},
methods: {
fn() {
console.log("我是 CompA 的方法");
}
}
}
</script>
// CompB.vue
<template>
<div>
<span>我是CompB组件</span>
<button @click="aFn">调用 CompA 的fn</button>
</div>
</template>
<script>
export default {
// 注入依赖
inject: ["aValue", "aFn"],
mounted() {
console.log("CompA 组件的 value", this.aValue);
}
}
</script>
4、parent和children
- parent和children 用于有 直接父子关系 的 组件 之间的通信
- $parent 用于 获取组件的父组件实例
- $parent 用于 获取组件的子组件实例
5、attrs和listeners
- attrs和listeners 主要用于 获取 父组件 的属性和方法
- $attrs 用于获取父组件中 没有在子组件的props中接收的属性
- $listeners 用于获取父组件中的自定义事件
6、Vuex
Vuex 类似于一个仓库,可存放一些数据供全局调用,但是刷新页面就会消失,若需解决此问题,可选择将数据存储到 Vuex 的同时,将其存放到 sessionStorage 或者 localStorage 中即可。
Vuex 中包括 state、mutations、actions、getters、modules 几个部分。其中
- state:定义变量
- getters:获取state中变量的值
- mutations:修改 state 中的值;使用 commit 触发;只能进行同步操作
- actions:进行各种操作,可通过 dispatch 触发;可以进行异步操作
- modules:项目较大时,可对变量的管理进行拆分,拆分成多个js文件,每个js文件作为单个的module,然后统一注册到 index.js 中,供全局调用
示例代码:
// module.js
const state = {
modelCreateDataJsonStr: "",
modelSelectDataJsonStr: "",
modelClassDataJsonStr: ""
};
const getters = {
getModelCreateDataJsonStr: state => {
return state.modelCreateDataJsonStr;
},
getModelSelectDataJsonStr: state => {
return state.modelSelectDataJsonStr;
},
getModelClassDataJsonStr: state => {
return state.modelClassDataJsonStr;
}
};
const mutations = {
SET_MODEL_CREATE_DATA_JSON_STR(state, modelCreateDataJsonStr) {
state.modelCreateDataJsonStr = modelCreateDataJsonStr;
},
SET_MODEL_SELECT_DATA_JSON_STR(state, modelSelectDataJsonStr) {
state.modelSelectDataJsonStr = modelSelectDataJsonStr;
},
SET_MODEL_CLASS_DATA_JSON_STR(state, modelClassDataJsonStr) {
state.modelClassDataJsonStr = modelClassDataJsonStr;
}
};
const actions = {
setModelCreateDataJsonStr({commit}, modelCreateDataJsonStr) {
commit("SET_MODEL_CREATE_DATA_JSON_STR", modelCreateDataJsonStr);
},
setModelSelectDataJsonStr({commit}, modelSelectDataJsonStr) {
commit("SET_MODEL_SELECT_DATA_JSON_STR", modelSelectDataJsonStr);
},
setModelClassDataJsonStr({commit}, modelClassDataJsonStr) {
commit("SET_MODEL_CLASS_DATA_JSON_STR", modelClassDataJsonStr);
}
};
// 暴露仓库
export { state, getters, mutations, actions };
// index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
/**
* 批量导入module下的所有模块
* 如果module下面有自己新建的文件夹 文件夹里面的JS文件不能用index命名 容易冲突
*/
const path = require('path');
const files = require.context('./module', true, /\.js$/);
const modules = {};
files.keys().forEach(key => {
const name = path.basename(key, '.js'); // 返回key的最后一部分
modules[name] = files(key).default || files(key);
});
Vue.use(Vuex);
const store = new Vuex.Store({
modules: modules,
});
export default store;
前言组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互进行直接的引用,所以组件间的相互通信是非常重要的。除了使用vuex外还有下面6种组件间的通信方式:1、 props / $emit父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信
(1)父组件向子组件传值(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>123456789101112131415161718192021222324子组件:
// 子组件<template> <div id="son"> <p>{{msg}}</p> <button @click="fn">按钮</button> </div></template><script>export default { name: "son", props: ["msg", "fn"]};</script>12345678910111213(2)子组件向父组件传递数据($emit的用法)$emit的特点:
$emit 绑定一个自定义事件,当这个事件被执行的时候就会将参数传递给父组件,而父组件通过v-on监听并接收参数用法:
父组件:
// 父组件<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: 'comArticle', components: { comArticle }, data() { return { currentIndex: -1, articleList: ['红楼梦', '西游记', '三国演义'] } }, methods: { onEmitIndex(idx) { this.currentIndex = idx } }}</script>1234567891011121314151617181920212223242526子组件:
//子组件<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>12345678910111213141516172、ref / $refs这种方式也是实现父子组件之间的通信
ref:这个属性用在子组件上,它的用用就指向了子组件的实例,可以通过实例来访问组件的数据和方法
用法:
在子组件中:
export default { data () { return { name: 'JavaScript' } }, methods: { sayHello () { console.log('hello') } }}123456789101112在父组件中:
<template> <child ref="child"></component-a></template><script> import child from './child.vue' export default { components: { child }, mounted () { console.log(this.$refs.child.name); // JavaScript this.$refs.child.sayHello(); // hello } }</script>123456789101112133、eventBus事件总线($emit / $on)eventBus事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下:
(1)创建事件中心管理组件之间的通信// event-bus.js
import Vue from 'vue'export const EventBus = new Vue()1234(2)发送事件 假设有两个兄弟组件firstCom和secondCom:firstCom和secondCom的父组件:
<template> <div> <first-com></first-com> <second-com></second-com> </div></template>
<script>import firstCom from './firstCom.vue'import secondCom from './secondCom.vue'export default { components: { firstCom, secondCom }}</script>1234567891011121314在firstCom组件中发送事件:
<template> <div> <button @click="add">加法</button> </div></template>
<script>import {EventBus} from './event-bus.js' // 引入事件中心
export default { data(){ return{ num:0 } }, methods:{ add(){ EventBus.$emit('addition', { num:this.num++ }) } }}</script>123456789101112131415161718192021222324(3)接收事件在secondCom组件中接收事件:
<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>12345678910111213141516171819在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。
4、依赖注入(provide / inject)这种方式就是vue中依赖注入,该方法用于 父子组件之间 的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方式来进行传值。就不用一层一层的传递数据了。
provide和inject是vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。
provide 钩子用来发送数据或方法。inject钩子用来接收数据或方法用法:父组件中:
provide() { return { num: this.num };}12345子组件中:
inject: ['num']1还有另一种写法,这种写法可以访问父组件中的所有属性:
provide() { return { app: this };}data() { return { num: 1 };}
inject: ['app']console.log(this.app.num)12345678910111213注意: 依赖注入所提供的属性是非响应式的。
5、$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>123456789101112131415161718192021父组件中:
<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>12345678910111213141516171819202122232425在上面的代码中,子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。
注意:
通过 $parent 访问到的是上一级父组件的实例,可以使用 $root 来访问根组件的实例在组件中使用$children拿到的是所有的子组件的实例,它是一个数组,并且是无序的在根组件 #app 上拿 $parent 得到的是 new Vue()的实例,在这实例上再拿 $parent 得到的是undefined,而在最底层的子组件拿 $children 是个空数组$children 的值是数组,而 $parent是个对象6、$attrs / $listeners考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给C组件传递数据,这种隔代传数据的情况该使用哪种方式呢?
如果是用props/ $emit 来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用vuex,如果仅仅是传递数据,那可能有点浪费了。
针对上述情况,vue引入了 $attrs / $listeners,实组件之间的跨代通信。
$attrs:继承所有的父组件属性(除了props传递的属性、class 和 style),一般用在子组件的子元素上$listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on=" $listeners " 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)再说一下 inheritAttrs
默认值为true,继承所有的父组件属性除props之外的所有属性。只继承class属性。$attrs / $listeners的用法:A组件(APP.vue):
<template> <div id="app"> //此处监听了两个事件,可以在B组件或者C组件中直接触发 <child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1> </div></template><script>import Child1 from './Child1.vue';export default { components: { Child1 }, methods: { onTest1() { console.log('test1 running'); }, onTest2() { console.log('test2 running'); } }};</script>1234567891011121314151617181920B组件(Child1.vue):
<template> <div class="child-1"> <p>props: {{pChild1}}</p> <p>$attrs: {{$attrs}}</p> <child2 v-bind="$attrs" v-on="$listeners"></child2> </div></template><script>import Child2 from './Child2.vue';export default { props: ['pChild1'], components: { Child2 }, inheritAttrs: false, mounted() { this.$emit('test1'); // 触发APP.vue中的test1方法 }};</script>123456789101112131415161718C 组件 (Child2.vue):
<template> <div class="child-2"> <p>props: {{pChild2}}</p> <p>$attrs: {{$attrs}}</p> </div></template><script>export default { props: ['pChild2'], inheritAttrs: false, mounted() { this.$emit('test2');// 触发APP.vue中的test2方法 }};</script>123456789101112131415在上述代码中:
C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性在B组件中通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)总结根据以上对这6种组件间的通信方法,可以将不同组件间的通信分为3种类型:父子组件间通信、兄弟组件间通信、任意组件间通信
1、父子组件间通信子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。2、兄弟组件间通信使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。通过 $parent / $refs 来获取到兄弟组件,也可以进行通信。3、任意组件间通信使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)