1.通过属性传值props🐖
父组件向子组件传送数据,这应该是最常用的方式了
子组件接收到数据之后,不能直接修改
父组件的数据。会报错,所以当父组件重新渲染时,数据会被覆盖。如果子组件内想要修改的话,推荐使用computedprops
可以是数组或对象,用于接收来自父组件的数据。
// 父组件 List.vue <template> <div> <List-item :str="str" :obj="obj" :arr="arr"></List-item> </div> </template> <script> import ListItem from "./ListItem"; export default { data() { return { str: "给子组件传值", obj: {msg: "给子组件传值"}, arr: [1, 2, 3] } }, components: { ListItem } } </script> // 子组件 ListItem.vue <template> <div> <div>{{msg}}</div> <div>{{obj}}</div> <div>{{arr}}</div> </div> </template> <script> export default { props: { msg: String, // props是字符串 obj: Object, // props是对象 arr: Array // props是数组 } } </script>
2.修饰符 .sync🐖
修饰符 .sync,它对 props
起到了一种修饰的作用,使用 .sync
进行修饰的 props
意味子组件有修改它的意图,这种情况下它只起到一个标注性作用,有它没它都不会影响逻辑
使用 .sync
修改上边的代码:
// 父组件 List.vue <template> <!-- 这里不写 .sync 也不会影响结果 --> <List-item :title.sync="title" @update:title="updataTitle"></List-item> </template> <script> import ListItem from "./ListItem"; export default { data() { return { title: "我是title", } }, components: { ListItem }, methods: { updataTitle(res) { this.title = res; } } } </script> // 子组件 ListItem.vue <template> <div> <button @click="handleClick">Click me</button> <div>{{title}}</div> </div> </template> <script> export default { props: { title: String, }, methods: { handleClick() { // 子组件向父组件传值 this.$emit('update:title', '我要父组件更新 title'); } } } </script>
3.使用.sync
向子组件传递 多个props:🐖
当我们用一个对象同时设置多个 prop
的时候,也可以将这个 .sync
修饰符和 v-bind
配合使用:
<text-document v-bind.sync="doc"></text-document>
这样会把 doc
对象中的每一个属性 (如 title) 都作为一个独立的 prop
传进去,然后各自添加用于更新的 v-on 监听器
。
4.通过 ref 注册子组件引用🐖
尽管存在 prop
和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,可以通过 ref
特性为这个子组件赋予一个 ID 引用。
<template> <div> <List-item ref="item" :title="title"></List-item> <div>{{data}}</div> </div> </template> <script> import ListItem from "./List-item"; export default { data() { return { title: "我是title", data: "" } }, components: { ListItem }, mounted() { this.data = this.$refs.item.message; } } </script>
5.通过$parent获取父组件实例的方法或者属性🐖
这种方式,从严格意思上讲不是值的传递,而是一种"取"(不推荐直接通过实例进行值的获取)。
可以通过 Vue 的实例属性 $parent
获得父组件的实例,借助实例可以调用父实例中的方法,或者获取父实例上的属性,从而达到取值的目的。
// 父组件 List.vue ... <script> export default { data() { return { message: "hello children", msg: "hello" } }, methods: { sendMessage() { return this.message; } } } </script> // 子组件 ListItem.vue <template> <div> <div>{{data}}</div> <div>{{msg}}</div> </div> </template> <script> export default { data() { return { data: "", msg: "" } }, mounted() { this.data = this.$parent.sendMessage(); // 调用父实例中的方法 this.msg = this.$parent.msg; // 获取父实例中的属性 } } </script>
🐖🐖🐖:
- 通过
$parent
获取父实例this.$parent.event
。 - 通过
props
传递方法。 - 通过
$emit
监听父组件中的方法this.$emit("envnt")
。
6. 通过事件传值 $emit🐖
子组件使用 $emit
发送一个自定义事件,事件名称是一个字符串。
父组件使用指令 v-on
绑定子组件发送的自定义事件。
// 父组件 List.vue <template> <div> <!-- 监听自定义事件 --> <List-item v-on:welcome="getWelcome"></List-item> </div> </template> <script> import ListItem from "./List-item"; export default { components: { ListItem }, methods: { getWelcome(data) { alert(data) } } } </script> // 子组件 ListItem.vue <template> <button @click="handleClick">Click me</button> </template> <script> export default { methods: { handleClick() { // 使用 $emit 发送自定义事件 welcome this.$emit('welcome', 'hello'); } } } </script>
7.$children🐖
获取父组件下的所有子组件的实例,返回的是一个数组 使用范围:该属性只针对vue组件,与js中childNodes还是有区别的。
$ildren: 获取子组件实例集合
hildNodes: 获取子节点集合
使用方法:
<template> <A></A> <B></B> </template> <script> export default{ data(){}, mounted(){ // 通过$children可以获取到A和B两个子组件的实例 console.log('children:',this.$children) } } </script> 其中, this.$children[0] 可以获取到A组件的实例,一样的,我们可以使用A组件的属性以及他的方法。
8.Vuex🐖
Vuex介绍
Vuex
是一个专为 Vue.js 应用程序开发的状态管理模式
。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex
解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上
Vuex各个模块
state
:用于数据的存储,是store中的唯一数据源
getters
:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算mutations
:类似函数,改变state数据的唯一途径,且不能用于处理异步事件actions
:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作modules
:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
Vuex实例应用
这里我们先新建 store文件夹
, 对Vuex进行一些封装处理
在 store 文件夹下添加 index.js
文件
// index.js // 自动挂载指定目录下的store import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) let modules = {} // @/store/module 目录下的文件自动挂载为 store 模块 const subModuleList = require.context('@/store/modules', false, /.js$/) subModuleList.keys().forEach(subRouter => { const moduleName = subRouter.substring(2, subRouter.length - 3) modules[moduleName] = subModuleList(subRouter).default }) export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules })
在 store 文件夹下添加 module
文件夹,在module文件夹再新建 user.js
文件
// user.js import user from '@/utils/user.js' import userApi from '@/apis/user' import { OPEN_ACCOUNT_STAGE, STAGE_STATUS } from '@/constant' let getUserPromise = null export default { namespaced: true, state() { return { userInfo: null, // 用户信息 isLogined: !!user.getToken(), // 是否已经登录 } }, mutations: { // 更新用户信息 updateUser(state, payload) { state.isLogined = !!payload state.userInfo = payload }, }, actions: { // 获取当前用户信息 async getUserInfo(context, payload) { // forceUpdate 表示是否强制更新 if (context.state.userInfo && !payload?.forceUpdate) { return context.state.userInfo } if (!getUserPromise || payload?.forceUpdate) { getUserPromise = userApi.getUserInfo() } // 获取用户信息 try { const userInfo = await getUserPromise context.commit('updateUser', userInfo) } finally { getUserPromise = null } return context.state.userInfo }, // 登出 async logout(context, payload = {}) { // 是否手动退出 const { manual } = payload if (manual) { await userApi.postLogout() } user.clearToken() context.commit('updateUser', null) }, } }
然后在项目的 main.js
文件中引入
import Vue from 'vue' import App from '@/app.vue' import { router } from '@/router' import store from '@/store/index' const vue = new Vue({ el: '#app', name: 'root', router, store, render: h => h(App), })
封装完成,即可正常操纵
this.$store.state.user.isLogined this.$store.state.user.userInfo this.$store.commit('user/updateUser', {}) await this.$store.dispatch('user/logout', { manual: true })
9.eventBus🐖
ventBus
够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。
- Event 事件。它可以是任意类型。
- Subscriber 事件订阅者。在EventBus3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe(),并且指定线程模型,默认是POSTING。
- Publisher 事件的发布者。我们可以在任意线程里发布事件,一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。
10. provide / inject🐖
provide / inject
是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。
provide
选项应该是
- 一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。
inject
选项应该是:
- 一个字符串数组
- 一个对象(详情点击这里) 基本用法:
// 祖先组件 提供foo //第一种 export default { name: "father", provide() { return { foo: 'hello' } }, } //第二种 export default { name: "father", provide: { foo:'hello~~~~' }, } //后代组件 注入foo, 直接当做this.foo来用 export default { inject:['foo'], }
上面的两种用法有什么区别吗?
- 如果你只是传一个字符串,像上面的
hello
,那么是没有区别的,后代都能读到。 - 如果你需要
this
对象属性的值(如下所示代码),那么第二种是传不了的,后代组件拿不到数据。所以建议只写第一种
//当你传递对象给后代时 provide() { return { test: this.msg } },
注意:一旦注入了某个数据,比如上面示例中的 foo,那这个组件中就不能再声明 foo 这个数据了,因为它已经被父级占有。
provide 和 inject 绑定并不是可响应的。
这是刻意为之的。然而,如果你传入了一个可监听的对象
,那么其对象的属性还是可响应的。因为对象是引用类型。
先来个值类型的数据(也就是字符串)例子,不会响应
provide(){ return{ test:this.msg } }, data() { return { msg: "Welcome to Your Vue.js App", } } mounted(){ setTimeout(()=>{ this.msg = "halo world"; console.log(this._provided.msg) //log:Welcome to Your Vue.js App },3000) },
如上所示,这样做是不行的,打印出来的 _provided
中的数据并没有改,子组件取得值也没变。
你甚至可以直接给 this._provided.msg
赋值,但是即使是_provided.msg 里面的值改变了,子组件的取值,依然没有变。
当你的参数是对象的时候,就可以响应了,如下:
provide(){ return{ test:this.activeData } }, data() { return { activeData:{name:'halo'}, } } mounted(){ setTimeout(()=>{ this.activeData.name = 'world'; },3000) }
这就是vue官方中写道的对象的属性是可以响应的
provide/inject 实现全局变量
provide/inject不是只能从祖先传递给后代吗?是的,但是,如果我们绑定到最顶层的组件app.vue,是不是所有后代都接收到了,就是当做全局变量来用了。
//app.vue export default { name: 'App', provide(){ return{ app:this } }, data(){ return{ text:"it's hard to tell the night time from the day" } }, methods:{ say(){ console.log("Desperado, why don't you come to your senses?") } } } //其他所有子组件,需要全局变量的,只需要按需注入app即可 export default { inject:['foo','app'], mounted(){ console.log(this.app.text); // 获取app中的变量 this.app.say(); // 可以执行app中的方法,变身为全局方法! } }
provide/inject 实现页面刷新,不闪烁
- 用
vue-router
重新路由到当前页面,页面是不进行刷新的 - 采用
window.reload()
,或者router.go(0)
刷新时,整个浏览器进行了重新加载,闪烁,体验不好
那我们怎么做呢?
跟上面的原理差不多,我们只在控制路由的组件中写一个函数(使用v-if控制router-view的显示隐藏,这里的原理不作赘述),然后把这个函数传递给后代,然后在后代组件中调用这个方法即可刷新路由啦。
//app.vue <router-view v-if="isShowRouter"/> export default { name: 'App', provide() { return { reload: this.reload } }, data() { return { isShowRouter: true, } }, methods:{ reload() { this.isShowRouter = false; this.$nextTick(() => { this.isShowRouter = true; }) } } } //后代组件 export default { inject: ['reload'], }
这里 provide 使用了函数传递给后代,然后后代调用这个函数,这种思路,也是可以做子后代向父组件传参通讯的思路了。这里的原理,和 event 事件订阅发布就很像了
11.通过 $root 访问根实例🐖
通过 $root
,任何组件都可以获取当前组件树的根 Vue 实例
,通过维护根实例上的 data,就可以实现组件间的数据共享
。
//main.js 根实例 new Vue({ el: '#app', store, router, // 根实例的 data 属性,维护通用的数据 data: function () { return { author: '' } }, components: { App }, template: '<App/>', }); <!--组件A--> <script> export default { created() { this.$root.author = '于是乎' } } </script> <!--组件B--> <template> <div><span>本文作者</span>{{ $root.author }}</div> </template>
注意:通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。
12. attrs与attrs 与 attrs与listenter🐖
多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时
$attrs
:包含父作用域里除 class 和 style 除外的非 props 属性集合。通过this.attrs获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v−bind = " attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="attrs获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v−bind="attrs"
$listeners
:包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on=“$linteners”
使用方式是相同的:
// Parent.vue <template> <child :name="name" title="1111" ></child> </template export default{ data(){ return { name:"小解" } } } // Child.vue <template> // 继续传给孙子组件 <sun-child v-bind="$attrs"></sun-child> </template> export default{ props:["name"], // 这里可以接收,也可以不接收 mounted(){ // 如果props接收了name 就是 { title:1111 },否则就是{ name:"小解", title:1111 } console.log(this.$attrs) } }
总结
常见使用场景可以分为三类:
- 父子组件通信:
props
、$parent / $children
、provide / inject
、ref \ $refs
、$attrs / $listeners
- 兄弟组件通信:
eventBus
、vuex
、自己实现简单的 Store 模式
- 跨级通信:
eventBus
、Vuex
、自己实现简单的 Store 模式
、provide / inject
、$attrs / $listeners
以上就是vue的组件通讯方法总结大全的详细内容,更多关于vue组件通讯的资料请关注阿兔在线工具其它相关文章!