Vue2组件间通讯
1、组件之间的关系
1.1、父子组件
假如在App组件中,引用了Student组件,那么我们就把App和Student两个组件的关系定义为父子,App是父组件,而被引用的Student是子组件。
1.2、兄弟组件
如果在App组件中,不仅引用了Student组件,又引用了School组件,那么School组件也是App的子组件。
既然School和Student都是App的子组件,那么School和Student就是兄弟组件。
(资料图片)
1.3、爷孙组件
假如在Student组件中,引用了一个List组件,用于循环遍历学生的信息列表,那么List是Student的子组件。
而Student又是App的子组件,儿子的儿子是什么?坐过超市门口摇摇车的同学都知道,App是List的爷爷,List是App的孙子,那么App和List组件关系就是爷孙。
2、父子组件之间的通讯
什么是父子组件之间的通讯?就是传值,假如父组件有一个值要传给子组件,或者子组件有一个值要传给父组件,该怎么传?
2.1、子传父(绑定自定义事件)
第一种方式:自定义事件(使用@或v-on)
在父组件App.vue中定义一个自定义事件school,比如说:
<script> import School from "./components/School"; export default { name: "App", components: {School}, methods: { getSchoolName(name) { alert("我的子组件School给我传了一个学校名:" + name); } } }</script>
在子组件School.vue中去触发父组件定义的school事件:
学校名:{{ name }}
<script> export default { name: "School", data() { return { name: "test", } }, methods: { sendSchoolName() { // $emit触发School组件上的school事件 this.$emit("school", this.name); } } }</script>
第二种方式 自定义事件(使用ref)
父组件App.vue中,在生命周期钩子mouted中:
<script> import School from "./components/School"; export default { name: "App", components: {School}, methods: { getSchoolName(name) { alert("我的子组件School给我传了一个学校名:" + name); } }, mounted() { // 自定义事件 this.$refs.schoolComponent.$on("school", this.getSchoolName); // 比使用@或v-on的方式更加灵活,可以加条件判断,又或者使用$once自定义事件可以只触发一次 // this.$refs.schoolComponent.$once("school", this.getSchoolName); } }</script>
School.vue中的代码和上述一致。
2.2、子传父(解绑自定义事件)
解绑一个自定义事件
this.$off("school");
解绑多个自定义事件
this.$off(["school", "test"]);
解绑所有自定义事件
this.$off();
代码示例:
学校名:{{ name }}
<script>export default { name: "School", data() { return { name: "test", } }, methods: { sendSchoolName() { // 触发School组件上的school事件 this.$emit("school", this.name); // 触发School组件上的demo事件 this.$emit("demo"); }, unbind() { // this.$off("school"); // 解绑一个自定义事件 // this.$off(["school", "demo"]); // 解绑多个自定义事件 this.$off(); // 解绑所有的自定义事件 }, destroy() { this.$destroy(); // 销毁了当前组件的实例,销毁之后所有的School实例的自定义事件就全部不奏效了 } }}</script>
2.3、父传子(props)
通过props配置项,可以接受到外部过来的数据,通常用于父组件往子组件传递值。
传递数据
接收数据
// 第一种:只接收props: ["name"],// 第二种:限制类型props: { name: String},// 第三种:限制类型、必要性、指定默认值props: { name: { type: String, // 类型 required: true, // 必要性 // default: "张三", // 默认值,和required不可以同时使用,因为默认值是没有传值的时候默认值,都required了那肯定有值传过来 }}
注意:props中的值是只读的,无法直接修改,否则vue会发出警告。如果要修改props中传过来的值,那也要先存到data中再做修改。
代码示例:
父组件App.vue:
<script>import Student from "./components/Student.vue"export default { name: "App", components: { Student }}</script>
子组件Student.vue:
学生姓名:{{ name }}
学生年龄:{{ age }}
学生性别:{{ sex }}
<script>export default { name: "Student", data() { return { // 如果想修改props的值,得先放在data中,就不会报错 myAge: this.age } }, methods: { updateAge() { this.myAge = 100 } }, // props: ["name", "age", "sex"] // 简单接收(开发中这个写得多) // 接收的同时对数据进行类型限制 // props: { // name: String, // age: Number, // sex: String, // } // 接收的同时对数据进行类型限制 + 默认值的指定 + 必要值的限制 props: { name: { type: String, required: true, // 名字是必须的 }, age: { type: Number, default: 99, // age可传可不传,如果没传,默认值是99 }, sex: { type: String, required: true, } }}</script>
3、事件总线
如果有人看到这就会奇怪,怎么2中只讲了父子组件之间的通讯,那兄弟组件和爷孙组件该怎么通讯呢?难道兄弟和爷孙之间代沟太大,无法交流吗?
并非如此,这里要介绍一种可以适用于任意组件之间通讯的技术,兄弟和爷孙就可以利用这个技术进行通讯,这个技术叫做事件总线。
1、安装全局事件总线,在脚手架中main.js入口文件中进行全局安装$bus。
import Vue from "vue"import App from "./App.vue"Vue.config.productionTip = false// 创建Vue实例对象 --- vmnew Vue({ render: h => h(App), beforeCreate() { Vue.prototype.$bus = this; // 安装全局事件总线 },}).$mount("#app")
bus这个英文单词有公交车,也有总线的意思。顾名思义,公交车嘛,谁都可以上,都可以往上面存数据,取数据。
Vue.prototype.$bus = this,为什么这么写?
如果掌握了JS高级中的原型与原型链可以看看,否则可以跳过。
首先,Vue.prototype是Vue构造函数的原型对象,而上述代码中的new Vue({}),new出来的是Vue的实例对象,通常我们命名为vm。(Vue是构造函数,vm是Vue构造函数的实例对象)
众所周知,构造函数Vue的实例对象vm,是可以通过隐式原型链_proto_,访问到Vue.prototype(原型对象)上的属性和方法的,这就确保了vm可以访问到$bus上的数据。
其次,vm下面管理着很多组件,这些组件并不是Vue构造函数new出来的,而是VueComponent构造函数new出来的,通常命名为vc,vc有无数个,而vm只有一个,一人之下,管理着所有组件。如果不使用脚手架,就可以看得很清楚,如下代码示例:
组件化编程 <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script> Vue.config.productionTip = false; // 创建school组件 const school = Vue.extend({ // el:"#root" // 组件定义时一定不要写el template: ` 学校名:{{ schoolName }}
地址:{{ address }}
`, data() { return { schoolName: "test", address: "福建" } } }) // 创建student组件 const student = Vue.extend({ template: ` 学生名:{{ studentName }}
学生年龄:{{ age }}
`, data() { return { studentName: "jack", age: 18 } } }); // 创建vm new Vue({ el: "#root", // 注册组件(局部注册) components: { school, student } }) </script>
上述代码可以看出,vm只有一个,是new Vue出来的,而vm所管理的vc有多个,是通过Vue.extend这个API生成的,其实这个API内部就是帮我们做了new VueComponent。而VueComponent有一个重要的内置关系,就是:
- VueComponent.prototype._proto_===Vue.prototype
- VueComponent构造函数的原型对象的隐式原型链__proto__是指向Vue.prototype的,这也是为什么不直接new VueComponent的原因,就是要让Vue.extend这个API帮忙做这一步。
因此,既然vc实例对象,一样可以通过隐式原型链__proto__去访问到Vue原型对象。
至此,vm和vc都共享Vue.prototype上面的属性和方法,这也就解释了Vue.prototype.$bus = this的前半句,为什么要把$bus放到Vue.prototype上,就是为了让所有的组件都可以访问到$bus,这辆公交车。
而this是什么呢?自然是vm。
为什么$bus要等于vm?为了调用vm上面的自定义事件API,没错,事件总线的本质依旧是自定义事件,代码示例如下:
兄弟组件School和Student之间进行通讯:
School.vue(在$bus上面自定义或者发布一个hello事件,来收数据):
学校名:{{ name }}
<script>export default { name: "School", data() { return { name: "test", } }, mounted() { this.$bus.$on("hello", (data) => { console.log("我是School组件,我收到了数据", data); }); }, beforeDestroy() { // 如果School组件被销毁,就同时解绑$bus上面注册或发布的自定义事件,这一步很有必要 this.$bus.$off("hello"); }}</script>
Student.vue(触发$bus上面的hello事件,来传数据):
学生姓名:{{ name }}
学生性别:{{ sex }}
<script>export default { name: "Student", data() { return { name: "张三", sex: "男", } }, methods: { sendStudentName() { this.$bus.$emit("hello", this.name); } }}</script>
ok,以上就是兄弟组件通过事件总线进行数据通讯,爷孙组件甚至曾爷爷曾孙子都一样可以通过事件总线进行通讯。
可能会萌新会问一些无聊的问题,比如说School和Student为什么是兄弟,不是姐妹?亦或者它们之间谁是兄,谁是弟之类的无聊问题。
我的解答是,这只是一种好理解的说法,它们之间没有长幼关系,而姐妹,如果你喜欢也可以叫姐妹。
任意组件之间进行通讯,除了事件总线的方法,还可以通过安装一些第三方库来进来通讯,比如消息的订阅与发布库pubsub.js,有兴趣可以去了解一下,和事件总线很相似。
关键词: